/*
 * UAE - The Un*x Amiga Emulator
 *
 * Win32 DirectInput/Windows XP RAWINPUT interface
 *
 * Copyright 2002 - 2010 Toni Wilen
 */

#include "sysconfig.h"
#include "dinput.h"
#include "clipboard.h"
#include "keyboard.h"
#include "inputdevice.h"
#include "uae.h"
#include "rtgmodes.h"
#include "dxwrap.h"
#include "drawing.h"
#include "win32.h"

extern "C"
{
    #include <hidsdi.h>
}

#define IGNOREEVERYTHING 0

#define MAX_MAPPINGS 256

#define DID_MOUSE 1
#define DID_JOYSTICK 2
#define DID_KEYBOARD 3

#define DIDC_DX 1
#define DIDC_RAW 2
#define DIDC_WIN 3
#define DIDC_CAT 4
#define DIDC_PARJOY 5

#define AXISTYPE_NORMAL 0
#define AXISTYPE_POV_X 1
#define AXISTYPE_POV_Y 2
#define AXISTYPE_SLIDER 3
#define AXISTYPE_DIAL 4

struct didata
{
    int type;
    int acquired;
    int priority;
    int superdevice;
    GUID iguid;
    GUID pguid;
    CString name;
    bool fullname;
    CString sortname;
    CString configname;
    int vid, pid, mi;

    int connection;
    LPDIRECTINPUTDEVICE8 lpdi;
    HANDLE rawinput;
    HIDP_CAPS hidcaps;
    PCHAR hidbuffer, hidbufferprev;
    PHIDP_PREPARSED_DATA hidpreparseddata;
    int maxusagelistlength;
    PUSAGE_AND_PAGE usagelist, prevusagelist;
    // int wininput;
    // int catweasel;
    int coop;

    HANDLE parjoy;
    PAR_QUERY_INFORMATION oldparjoystatus;

    short axles;
    short buttons, buttons_real;
    short axismappings[MAX_MAPPINGS];
    CString axisname[MAX_MAPPINGS];
    short axissort[MAX_MAPPINGS];
    short axistype[MAX_MAPPINGS];
    int axismax[MAX_MAPPINGS];
    int axismin[MAX_MAPPINGS];
    short axisusagepage[MAX_MAPPINGS];
    short buttonmappings[MAX_MAPPINGS];
    CString buttonname[MAX_MAPPINGS];
    short buttonsort[MAX_MAPPINGS];

    short buttonaxisparent[MAX_MAPPINGS];
    short buttonaxisparentdir[MAX_MAPPINGS];

    void zeroes()
    {
        memset(&iguid, 0, sizeof(iguid));
        memset(&pguid, 0, sizeof(pguid));
        vid = 0;
        pid = 0;
        mi = 0;
        connection = 0;
        memset(&lpdi, 0, sizeof(lpdi));
        rawinput = 0;
        // wininput = 0;
        coop = 0;

        parjoy = INVALID_HANDLE_VALUE;
        memset(&oldparjoystatus, 0, sizeof(oldparjoystatus));

        buttons = 0;
        buttons_real = 0;
        memset(&axismappings, 0, sizeof(axismappings));

        memset(&axissort, 0, sizeof(axissort));
        memset(&axistype, 0, sizeof(axistype));
        memset(&axismax, 0, sizeof(axismax));
        memset(&axismin, 0, sizeof(axismin));
        memset(&axisusagepage, 0, sizeof(axisusagepage));
        memset(&buttonmappings, 0, sizeof(buttonmappings));
        memset(&buttonsort, 0, sizeof(buttonsort));
        memset(&buttonaxisparent, 0, sizeof(buttonaxisparent));
        memset(&buttonaxisparentdir, 0, sizeof(buttonaxisparentdir));

        axles = 0;
        type = 0;
        acquired = 0;
        priority = 0;
        superdevice = 0;

        for (int i = 0; i < MAX_INPUT_DEVICES; i++)
        {
            buttonname[i] = "";
            axisname[i] = "";
        }

        sortname = "";
        configname = "";
        name = "";
        fullname = false;

        memset(&hidcaps, 0, sizeof(hidcaps));
        maxusagelistlength = 0;
        if (hidpreparseddata)
            HidD_FreePreparsedData(hidpreparseddata);
        free(hidbuffer);
        free(hidbufferprev);
        free(usagelist);
        free(prevusagelist);
    }
};

#define MAX_PARJOYPORTS 2

#define DI_BUFFER 30
#define DI_KBBUFFER 50

static LPDIRECTINPUT8 g_lpdi;

static struct didata di_mouse[MAX_INPUT_DEVICES];
static struct didata di_keyboard[MAX_INPUT_DEVICES];
static struct didata di_joystick[MAX_INPUT_DEVICES];
static int num_mouse, num_keyboard, num_joystick;
static int dd_inited, mouse_inited, keyboard_inited, joystick_inited;
static int stopoutput;
static HANDLE kbhandle = INVALID_HANDLE_VALUE;
static int oldleds, oldusedleds, newleds, disabledleds;
static int normalmouse, supermouse, rawmouse /*, winmouse, winmousenumber, winmousemode, winmousewheelbuttonstart*/;
static int normalkb, superkb, rawkb;
static int rawhid;
static bool rawinput_enabled_mouse, rawinput_enabled_keyboard;
HWND rawinput_window_handle = NULL;
bool rawinput_enabled_hid = false;
static bool rawinput_decided;

static short axisold[MAX_INPUT_DEVICES][256];

int no_rawinput = 0;
int dinput_enum_all;

// int dinput_winmouse()
// {
//     if (winmouse)
//         return winmousenumber;
//     return -1;
// }

// int dinput_wheelbuttonstart()
// {
//     return winmousewheelbuttonstart;
// }

// int dinput_winmousemode()
// {
//     if (winmouse)
//         return winmousemode;
//     return 0;
// }

#if 0
static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    Logger::Write(L"*");
    if (nCode >= 0)
    {
        KBDLLHOOKSTRUCT* k = (KBDLLHOOKSTRUCT*)lParam;
        int vk = k->vkCode;
        int sc = k->scanCode;
        Logger::Write(L"%02x %02x\n", vk, sc);
    }
    return CallNextHookEx(nullptr, nCode, wParam, lParam);
}

static HHOOK kbhook;
static void lock_kb()
{
    if (kbhook)
        return;
    kbhook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hInst, 0);
    if (!kbhook)
        Logger::Write(L"SetWindowsHookEx %d\n", GetLastError());
    else
        Logger::Write(L"***************************\n");
}
static void unlock_kb()
{
    if (!kbhook)
        return;
    Logger::Write(L"!!!!!!!!!!!!!!!!!!!!!!!!\n");
    UnhookWindowsHookEx(kbhook);
    kbhook = nullptr;
}
#endif

static BYTE ledkeystate[256];

static uint get_leds()
{
    uint led = 0;

    GetKeyboardState(ledkeystate);
    if (ledkeystate[VK_NUMLOCK] & 1)
        led |= KBLED_NUMLOCKM;
    if (ledkeystate[VK_CAPITAL] & 1)
        led |= KBLED_CAPSLOCKM;
    if (ledkeystate[VK_SCROLL] & 1)
        led |= KBLED_SCROLLLOCKM;

    if (g_curr_conf.win32_kbledmode)
    {
        oldleds = led;
    }
    else if (!g_curr_conf.win32_kbledmode && kbhandle != INVALID_HANDLE_VALUE)
    {
        // #ifdef WINDDK
        KEYBOARD_INDICATOR_PARAMETERS InputBuffer;
        KEYBOARD_INDICATOR_PARAMETERS OutputBuffer;
        ULONG DataLength = sizeof(KEYBOARD_INDICATOR_PARAMETERS);
        ULONG ReturnedLength;

        memset(&InputBuffer, 0, sizeof(InputBuffer));
        memset(&OutputBuffer, 0, sizeof(OutputBuffer));
        if (!DeviceIoControl(kbhandle, IOCTL_KEYBOARD_QUERY_INDICATORS,
                &InputBuffer, DataLength, &OutputBuffer, DataLength, &ReturnedLength, nullptr))
            return 0;
        led = 0;
        if (OutputBuffer.LedFlags & KEYBOARD_NUM_LOCK_ON)
            led |= KBLED_NUMLOCKM;
        if (OutputBuffer.LedFlags & KEYBOARD_CAPS_LOCK_ON)
            led |= KBLED_CAPSLOCKM;
        if (OutputBuffer.LedFlags & KEYBOARD_SCROLL_LOCK_ON)
            led |= KBLED_SCROLLLOCKM;
        // #endif
    }
    return led;
}

static void kbevt(byte vk, byte sc)
{
    keybd_event(vk, 0, 0, 0);
    keybd_event(vk, 0, KEYEVENTF_KEYUP, 0);
}

static void set_leds(uint led)
{
    if (g_curr_conf.win32_kbledmode)
    {
        if ((oldleds & KBLED_NUMLOCKM) != (led & KBLED_NUMLOCKM) && !(disabledleds & KBLED_NUMLOCKM))
        {
            kbevt(VK_NUMLOCK, 0x45);
            oldleds ^= KBLED_NUMLOCKM;
        }
        if ((oldleds & KBLED_CAPSLOCKM) != (led & KBLED_CAPSLOCKM) && !(disabledleds & KBLED_CAPSLOCKM))
        {
            kbevt(VK_CAPITAL, 0x3a);
            oldleds ^= KBLED_CAPSLOCKM;
        }
        if ((oldleds & KBLED_SCROLLLOCKM) != (led & KBLED_SCROLLLOCKM) && !(disabledleds & KBLED_SCROLLLOCKM))
        {
            kbevt(VK_SCROLL, 0x46);
            oldleds ^= KBLED_SCROLLLOCKM;
        }
    }
    else if (kbhandle != INVALID_HANDLE_VALUE)
    {
        // #ifdef WINDDK
        KEYBOARD_INDICATOR_PARAMETERS InputBuffer;
        ULONG DataLength = sizeof(KEYBOARD_INDICATOR_PARAMETERS);
        ULONG ReturnedLength;

        memset(&InputBuffer, 0, sizeof(InputBuffer));
        if (led & KBLED_NUMLOCKM)
            InputBuffer.LedFlags |= KEYBOARD_NUM_LOCK_ON;
        if (led & KBLED_CAPSLOCKM)
            InputBuffer.LedFlags |= KEYBOARD_CAPS_LOCK_ON;
        if (led & KBLED_SCROLLLOCKM)
            InputBuffer.LedFlags |= KEYBOARD_SCROLL_LOCK_ON;
        if (!DeviceIoControl(kbhandle, IOCTL_KEYBOARD_SET_INDICATORS,
                &InputBuffer, DataLength, nullptr, 0, &ReturnedLength, nullptr))
            Logger::Write(L"kbleds: DeviceIoControl() failed %d\n", GetLastError());
        // #endif
    }
}

static void update_leds()
{
    if (!g_curr_conf.keyboard_leds_in_use)
        return;
    if (newleds != oldusedleds)
    {
        oldusedleds = newleds;
        set_leds(newleds);
    }
}

void indicator_leds(int num, int state)
{
    int i;

    if (!g_curr_conf.keyboard_leds_in_use)
        return;
    disabledleds = 0;
    for (i = 0; i < 3; i++)
    {
        if (g_curr_conf.keyboard_leds[i] == num + 1)
        {
            newleds &= ~(1 << i);
            if (state)
                newleds |= 1 << i;
        }
        else if (g_curr_conf.keyboard_leds[i] <= 0)
        {
            disabledleds |= 1 << i;
        }
    }
}

static int isrealbutton(struct didata* did, int num)
{
    if (num >= did->buttons)
        return 0;
    if (did->buttonaxisparent[num] >= 0)
        return 0;
    return 1;
}

static void fixbuttons(struct didata* did)
{
    if (did->buttons > 0)
        return;
    Logger::Write(L"'%s' has no buttons, adding single default button\n", did->name);
    did->buttonmappings[0] = DIJOFS_BUTTON(0);
    did->buttonsort[0] = 0;
    did->buttonname[0] = L"Button";
    did->buttons++;
}

static void addplusminus(struct didata* did, int i)
{
    TCHAR tmp[256];
    int j;

    if (did->buttons + 1 >= MAX_MAPPINGS)
        return;
    for (j = 0; j < 2; j++)
    {
        _stprintf(tmp, L"%s [%c]", did->axisname[i], j ? '+' : '-');
        did->buttonname[did->buttons] = tmp;
        did->buttonmappings[did->buttons] = did->axismappings[i];
        did->buttonsort[did->buttons] = 1000 + (did->axismappings[i] + did->axistype[i]) * 2 + j;
        did->buttonaxisparent[did->buttons] = i;
        did->buttonaxisparentdir[did->buttons] = j;
        did->buttons++;
    }
}

static void fixthings(struct didata* did)
{
    int i;

    did->buttons_real = did->buttons;
    for (i = 0; i < did->axles; i++)
        addplusminus(did, i);
}
static void fixthings_mouse(struct didata* did)
{
    int i;

    did->buttons_real = did->buttons;
    for (i = 0; i < did->axles; i++)
    {
        if (did->axissort[i] == -97)
            addplusminus(did, i);
    }
}

static int rawinput_available, rawinput_registered_mouse, rawinput_registered_kb, rawinput_registered_hid;

static bool test_rawinput(int usage)
{
    RAWINPUTDEVICE rid = { 0 };

    rid.usUsagePage = 1;
    rid.usUsage = usage;
    if (RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)) == FALSE)
    {
        Logger::Write(L"RAWINPUT test failed, usage=%d ERR=%d\n", usage, GetLastError());
        return false;
    }
    rid.dwFlags |= RIDEV_REMOVE;
    if (RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)) == FALSE)
    {
        Logger::Write(L"RAWINPUT test failed (release), usage=%d, ERR=%d\n", usage, GetLastError());
        return false;
    }
    return true;
}

static int register_rawinput(int flags)
{
    if (!rawinput_available)
        return 0;

    bool rawinput_mouse_devices = (rawmouse > 0);
    bool rawinput_keyboards_devices = (rawkb > 0);
    bool rawinput_hid_devices = (rawhid > 0);
    bool same_window = (hMainWnd == rawinput_window_handle);

    bool register_mouse = (rawinput_mouse_devices && !rawinput_registered_mouse) || !same_window;
    bool unregister_mouse = (!rawinput_mouse_devices && rawinput_registered_mouse) || !same_window;
    bool register_keyboard = (rawinput_keyboards_devices && !rawinput_registered_kb) || !same_window;
    bool unregister_keyboard = (!rawinput_keyboards_devices && rawinput_registered_kb) || !same_window;
    bool register_hid = (rawinput_hid_devices && !rawinput_registered_hid) || !same_window;
    bool unregister_hid = (!rawinput_hid_devices && rawinput_registered_hid) || !same_window;

    if (unregister_mouse)
    {
        if (Logging::DIRECTINPUT_RAW_DEBUG)
            Logger::WriteLine(L"register_rawinput #unregistering mouse");

        RAWINPUTDEVICE rid;
        memset(&rid, 0, sizeof(rid));

        rid.usUsagePage = 1;
        rid.usUsage = 2;
        rid.dwFlags = RIDEV_REMOVE;

        if (RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)) == FALSE)
            Logger::Write(L"RAWINPUT registration failed #unregistering mouse");
    }

    if (register_mouse)
    {
        if (Logging::DIRECTINPUT_RAW_DEBUG)
            Logger::WriteLine(L"register_rawinput #unregistering mouse");

        RAWINPUTDEVICE rid;
        memset(&rid, 0, sizeof(rid));

        rid.usUsagePage = 1;
        rid.usUsage = 2;
        rid.dwFlags = RIDEV_INPUTSINK;
        rid.hwndTarget = hMainWnd;

        if (RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)) == FALSE)
            Logger::Write(L"RAWINPUT registration failed #registering mouse");
    }

    if (unregister_keyboard)
    {
        if (Logging::DIRECTINPUT_RAW_DEBUG)
            Logger::WriteLine(L"register_rawinput #unregistering keyboard");

        RAWINPUTDEVICE rid;
        memset(&rid, 0, sizeof(rid));

        rid.usUsagePage = 1;
        rid.usUsage = 6;
        rid.dwFlags = RIDEV_REMOVE;

        if (RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)) == FALSE)
            Logger::Write(L"RAWINPUT registration failed #unregistering keyboard");
    }

    if (register_keyboard)
    {
        if (Logging::DIRECTINPUT_RAW_DEBUG)
            Logger::WriteLine(L"register_rawinput #registering keyboard");

        RAWINPUTDEVICE rid;
        memset(&rid, 0, sizeof(rid));

        rid.usUsagePage = 1;
        rid.usUsage = 6;
        rid.dwFlags = RIDEV_INPUTSINK | RIDEV_NOHOTKEYS;
        rid.hwndTarget = hMainWnd;

        if (RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)) == FALSE)
            Logger::Write(L"RAWINPUT registration failed #registering keyboard");
    }

    if (unregister_hid)
    {
        for (int i = 0; i < num_joystick; i++)
        {
            if (Logging::DIRECTINPUT_RAW_DEBUG)
                Logger::WriteLine(L"register_rawinput #unregistering hid");

            struct didata* did = &di_joystick[i];
            if (did->connection != DIDC_RAW)
                continue;

            RAWINPUTDEVICE rid;
            memset(&rid, 0, sizeof(rid));

            rid.usUsagePage = did->hidcaps.UsagePage;
            rid.usUsage = did->hidcaps.Usage;
            rid.dwFlags = RIDEV_REMOVE;

            if (RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)) == FALSE)
                Logger::Write(L"RAWINPUT registration failed #registering hid");
        }
    }

    if (register_hid)
    {
        for (int i = 0; i < num_joystick; i++)
        {
            if (Logging::DIRECTINPUT_RAW_DEBUG)
                Logger::WriteLine(L"register_rawinput #registering hid");

            struct didata* did = &di_joystick[i];
            if (did->connection != DIDC_RAW)
                continue;

            RAWINPUTDEVICE rid;
            memset(&rid, 0, sizeof(rid));

            rid.usUsagePage = did->hidcaps.UsagePage;
            rid.usUsage = did->hidcaps.Usage;
            rid.dwFlags = RIDEV_INPUTSINK;
            rid.hwndTarget = hMainWnd;

            if (RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE)) == FALSE)
                Logger::Write(L"RAWINPUT registration failed #registering hid");
        }
    }

    rawinput_registered_mouse = rawinput_mouse_devices;
    rawinput_registered_kb = rawinput_keyboards_devices;
    rawinput_registered_hid = rawinput_hid_devices;
    rawinput_window_handle = hMainWnd;

    return 1;
}

static void cleardid(struct didata* did)
{
    did->name = "";
    did->sortname = "";
    did->configname = "";

    for (int i = 0; i < MAX_INPUT_DEVICES; i++)
    {
        did->axisname[i] = "";
        did->buttonname[i] = "";
    }

    did->zeroes();

    for (int i = 0; i < MAX_MAPPINGS; i++)
    {
        did->axismappings[i] = -1;
        did->buttonmappings[i] = -1;
        did->buttonaxisparent[i] = -1;
    }
}

#define MAX_KEYCODES 256
static byte di_keycodes[MAX_INPUT_DEVICES][MAX_KEYCODES];
static int keyboard_german;

static int keyhack(int scancode, int pressed, int num)
{
    static byte backslashstate, apostrophstate;

    // #ifdef RETROPLATFORM
    // if (rp_checkesc(scancode, di_keycodes[num], pressed, num))
    //     return -1;
    // #endif

    // check ALT-F4
    if (pressed && !di_keycodes[num][DIK_F4] && scancode == DIK_F4)
    {
        if (di_keycodes[num][DIK_LALT] && !g_curr_conf.win32_ctrl_F11_is_quit)
        {
            // #ifdef RETROPLATFORM
            // if (rp_close())
            //     return -1;
            // #endif
            uae_quit();
            return -1;
        }
    }
#ifdef SINGLEFILE
    if (pressed && scancode == DIK_ESCAPE)
    {
        uae_quit();
        return -1;
    }
#endif

    //  release mouse if TAB and ALT is pressed
    if (pressed && di_keycodes[num][DIK_LALT] && scancode == DIK_TAB)
    {
        disablecapture();
        return -1;
    }

    if (!keyboard_german)
        return scancode;

    // This code look so ugly because there is no Directinput
    // key for # (called numbersign on win standard message)
    // available
    // so here need to change qulifier state and restore it when key
    // is release
    if (scancode == DIK_BACKSLASH) // The # key
    {
        if (di_keycodes[num][DIK_LSHIFT] || di_keycodes[num][DIK_RSHIFT] || apostrophstate)
        {
            if (pressed)
            {
                apostrophstate = 1;
                inputdevice_translatekeycode(num, DIK_RSHIFT, 0);
                inputdevice_translatekeycode(num, DIK_LSHIFT, 0);
                return 13;       // the german ' key
            }
            else
            {
                // best is add a real keystatecheck here but it still work so
                apostrophstate = 0;
                inputdevice_translatekeycode(num, DIK_LALT, 0);
                inputdevice_translatekeycode(num, DIK_LSHIFT, 0);
                inputdevice_translatekeycode(num, 4, 0);   // release also the # key
                return 13;
            }
        }
        if (pressed)
        {
            inputdevice_translatekeycode(num, DIK_LALT, 1);
            inputdevice_translatekeycode(num, DIK_LSHIFT, 1);
            return 4;           // the german # key
        }
        else
        {
            inputdevice_translatekeycode(num, DIK_LALT, 0);
            inputdevice_translatekeycode(num, DIK_LSHIFT, 0);
            //  Here is the same not nice but do the job
            return 4;           // the german # key
        }
    }
    if ((di_keycodes[num][DIK_RALT]) || (backslashstate))
    {
        switch (scancode)
        {
            case 12:
                if (pressed)
                {
                    backslashstate = 1;
                    inputdevice_translatekeycode(num, DIK_RALT, 0);
                    return DIK_BACKSLASH;
                }
                else
                {
                    backslashstate = 0;
                    return DIK_BACKSLASH;
                }
        }
    }
    return scancode;
}

// #ifdef TABLET
//
// static int tablet;
// static int axmax, aymax, azmax;
// static int xmax, ymax, zmax;
// static int xres, yres;
// static int maxpres;
// static TCHAR* tabletname;
// static int tablet_x, tablet_y, tablet_z, tablet_pressure, tablet_buttons, tablet_proximity;
// static int tablet_ax, tablet_ay, tablet_az, tablet_flags;
//
// static void tablet_send()
// {
//     static int eraser;
//
//     if ((tablet_flags & TPS_INVERT) && tablet_pressure > 0)
//     {
//         tablet_buttons |= 2;
//         eraser = 1;
//     }
//     else if (eraser)
//     {
//         tablet_buttons &= ~2;
//         eraser = 0;
//     }
//     if (tablet_x < 0)
//         return;
//     inputdevice_tablet(tablet_x, tablet_y, tablet_z, tablet_pressure, tablet_buttons, tablet_proximity,
//         tablet_ax, tablet_ay, tablet_az);
// }
//
// void send_tablet_proximity(int inproxi)
// {
//     if (tablet_proximity == inproxi)
//         return;
//     tablet_proximity = inproxi;
//     if (!tablet_proximity)
//     {
//         tablet_flags &= ~TPS_INVERT;
//     }
//     tablet_send();
// }
//
// void send_tablet(int x, int y, int z, int pres, uint buttons, int flags, int ax, int ay, int az, int rx, int ry, int rz, RECT* r)
// {
//     //Logger::Write(L"%d %d %d (%d,%d,%d), %08X %d\n", x, y, pres, ax, ay, az, buttons, proxi);
//     if (axmax > 0)
//         ax = ax * 255 / axmax;
//     else
//         ax = 0;
//     if (aymax > 0)
//         ay = ay * 255 / aymax;
//     else
//         ay = 0;
//     if (azmax > 0)
//         az = az * 255 / azmax;
//     else
//         az = 0;
//     pres = pres * 255 / maxpres;
//
//     tablet_x = x;
//     tablet_y = ymax - y;
//     tablet_z = z;
//     tablet_pressure = pres;
//     tablet_buttons = buttons;
//     tablet_ax = abs(ax);
//     tablet_ay = abs(ay);
//     tablet_az = abs(az);
//     tablet_flags = flags;
//
//     tablet_send();
// }
//
// static int gettabletres(AXIS* a)
// {
//     FIX32 r = a->axResolution;
//     switch (a->axUnits)
//     {
//     case TU_INCHES:
//         return r >> 16;
//     case TU_CENTIMETERS:
//         return (int)(((r / 65536.0) / 2.54) + 0.5);
//     default:
//         return -1;
//     }
// }
//
// void * open_tablet(HWND hwnd)
// {
// #ifndef _WIN64
//     static int initialized;
//     LOGCONTEXT lc;
//     AXIS tx = { 0 }, ty = { 0 }, tz = { 0 };
//     AXIS pres = { 0 };
//     int xm, ym, zm;
//
//     if (!tablet)
//         return 0;
//     xmax = -1;
//     ymax = -1;
//     zmax = -1;
//     WTInfo(WTI_DEFCONTEXT, 0, &lc);
//     WTInfo(WTI_DEVICES, DVC_X, &tx);
//     WTInfo(WTI_DEVICES, DVC_Y, &ty);
//     WTInfo(WTI_DEVICES, DVC_NPRESSURE, &pres);
//     WTInfo(WTI_DEVICES, DVC_XMARGIN, &xm);
//     WTInfo(WTI_DEVICES, DVC_YMARGIN, &ym);
//     WTInfo(WTI_DEVICES, DVC_ZMARGIN, &zm);
//     xmax = tx.axMax;
//     ymax = ty.axMax;
//     if (WTInfo(WTI_DEVICES, DVC_Z, &tz))
//         zmax = tz.axMax;
//     lc.lcOptions |= CXO_MESSAGES;
//     lc.lcPktData = PACKETDATA;
//     lc.lcPktMode = PACKETMODE;
//     lc.lcMoveMask = PACKETDATA;
//     lc.lcBtnUpMask = lc.lcBtnDnMask;
//     lc.lcInExtX = tx.axMax;
//     lc.lcInExtY = ty.axMax;
//     if (zmax > 0)
//         lc.lcInExtZ = tz.axMax;
//     if (!initialized)
//     {
//         Logger::Write(L"Tablet '%s' parameters\n", tabletname);
//         Logger::Write(L"Xmax=%d,Ymax=%d,Zmax=%d\n", xmax, ymax, zmax);
//         Logger::Write(L"Xres=%.1f:%d,Yres=%.1f:%d,Zres=%.1f:%d\n",
//             tx.axResolution / 65536.0, tx.axUnits, ty.axResolution / 65536.0, ty.axUnits, tz.axResolution / 65536.0, tz.axUnits);
//         Logger::Write(L"Xrotmax=%d,Yrotmax=%d,Zrotmax=%d\n", axmax, aymax, azmax);
//         Logger::Write(L"PressureMin=%d,PressureMax=%d\n", pres.axMin, pres.axMax);
//     }
//     maxpres = pres.axMax;
//     xres = gettabletres(&tx);
//     yres = gettabletres(&ty);
//     tablet_proximity = -1;
//     tablet_x = -1;
//     inputdevice_tablet_info(xmax, ymax, zmax, axmax, aymax, azmax, xres, yres);
//     initialized = 1;
//     return WTOpen(hwnd, &lc, TRUE);
// #else
//     return 0;
// #endif
// }
//
// int close_tablet(void* ctx)
// {
// #ifndef _WIN64
//     if (ctx != nullptr)
//         WTClose((HCTX)ctx);
//     ctx = nullptr;
//     if (!tablet)
//         return 0;
//     return 1;
// #else
//     return 0;
// #endif
// }
//
// int is_tablet()
// {
//     return tablet ? 1 : 0;
// }
//
// static int initialize_tablet()
// {
// #ifndef _WIN64
//     HMODULE h;
//     TCHAR name[MAX_DPATH];
//     struct tagAXIS ori[3];
//     int tilt = 0;
//
//     h = LoadLibrary(L"wintab32.dll");
//     if (h == nullptr)
//     {
//         Logger::Write(L"Tablet: no wintab32.dll\n");
//         return 0;
//     }
//     FreeLibrary(h);
//     if (!WTInfo(0, 0, nullptr))
//     {
//         Logger::Write(L"Tablet: WTInfo() returned failure\n");
//         return 0;
//     }
//     name[0] = 0;
//     WTInfo(WTI_DEVICES, DVC_NAME, name);
//     axmax = aymax = azmax = -1;
//     tilt = WTInfo(WTI_DEVICES, DVC_ORIENTATION, &ori);
//     if (tilt)
//     {
//         if (ori[0].axMax > 0)
//             axmax = ori[0].axMax;
//         if (ori[1].axMax > 0)
//             aymax = ori[1].axMax;
//         if (ori[2].axMax > 0)
//             azmax = ori[2].axMax;
//     }
//     Logger::Write(L"Tablet '%s' detected\n", name);
//     tabletname = _tcsdup(name);
//     tablet = TRUE;
//     return 1;
// #else
//     return 0;
// #endif
// }
//
// #endif

// #if 0
// static int initialize_parjoyport()
// {
//     for (int i = 0; i < MAX_PARJOYPORTS && num_joystick < MAX_INPUT_DEVICES; i++)
//     {
//         struct didata* did;
//         TCHAR* p = i ? g_curr_conf.win32_parjoyport1 : g_curr_conf.win32_parjoyport0;
//         if (p[0] == 0)
//             continue;
//         HANDLE ph = CreateFile(p, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
//         if (ph == INVALID_HANDLE_VALUE)
//         {
//             Logger::Write(L"PARJOY: '%s' failed to open: %u\n", p, GetLastError());
//             continue;
//         }
//         Logger::Write(L"PARJOY: '%s' open\n", p);
//         for (int j = 0; j < 2; j++)
//         {
//             TCHAR tmp[100];
//             did = di_joystick;
//             did += num_joystick;
//             cleardid(did);
//             did->connection = DIDC_PARJOY;
//             did->parjoy = ph;
//             _stprintf(tmp, L"Parallel joystick %d.%d", i + 1, j + 1);
//             did->name = _tcsdup(tmp);
//             did->sortname = _tcsdup(tmp);
//             _stprintf(tmp, L"PARJOY%d.%d", i, j);
//             did->configname = _tcsdup(tmp);
//             did->buttons = did->buttons_real = 1;
//             did->axles = 2;
//             did->axissort[0] = 0;
//             did->axisname[0] = _tcsdup(L"X-Axis");
//             did->axissort[1] = 1;
//             did->axisname[1] = _tcsdup(L"Y-Axis");
//             for (j = 0; j < did->buttons; j++)
//             {
//                 did->buttonsort[j] = j;
//                 _stprintf(tmp, L"Button %d", j + 1);
//                 did->buttonname[j] = _tcsdup(tmp);
//             }
//             did->priority = -1;
//             fixbuttons(did);
//             fixthings(did);
//             num_joystick++;
//         }
//     }
//     return 0;
// }
// #endif

// static int initialize_catweasel()
// {
//     int j, i;
//     TCHAR tmp[MAX_DPATH];
//     struct didata* did;
//
//     if (catweasel_ismouse())
//     {
//         for (i = 0; i < 2 && num_mouse < MAX_INPUT_DEVICES; i++)
//         {
//             did = di_mouse;
//             did += num_mouse;
//             cleardid(did);
//             did->connection = DIDC_CAT;
//             did->catweasel = i;
//             _stprintf(tmp, L"Catweasel mouse");
//             did->name = _tcsdup(tmp);
//             did->sortname = _tcsdup(tmp);
//             _stprintf(tmp, L"CWMOUSE%d", i);
//             did->configname = _tcsdup(tmp);
//             did->buttons = did->buttons_real = 3;
//             did->axles = 2;
//             did->axissort[0] = 0;
//             did->axisname[0] = _tcsdup(L"X-Axis");
//             did->axissort[1] = 1;
//             did->axisname[1] = _tcsdup(L"Y-Axis");
//             for (j = 0; j < did->buttons; j++)
//             {
//                 did->buttonsort[j] = j;
//                 _stprintf(tmp, L"Button %d", j + 1);
//                 did->buttonname[j] = _tcsdup(tmp);
//             }
//             did->priority = -1;
//             num_mouse++;
//         }
//     }
//     if (catweasel_isjoystick())
//     {
//         for (i = 0; i < 2 && num_joystick < MAX_INPUT_DEVICES; i++)
//         {
//             did = di_joystick;
//             did += num_joystick;
//             cleardid(did);
//             did->connection = DIDC_CAT;
//             did->catweasel = i;
//             _stprintf(tmp, L"Catweasel joystick");
//             did->name = _tcsdup(tmp);
//             did->sortname = _tcsdup(tmp);
//             _stprintf(tmp, L"CWJOY%d", i);
//             did->configname = _tcsdup(tmp);
//             did->buttons = did->buttons_real = (catweasel_isjoystick() & 0x80) ? 3 : 1;
//             did->axles = 2;
//             did->axissort[0] = 0;
//             did->axisname[0] = _tcsdup(L"X-Axis");
//             did->axissort[1] = 1;
//             did->axisname[1] = _tcsdup(L"Y-Axis");
//             for (j = 0; j < did->buttons; j++)
//             {
//                 did->buttonsort[j] = j;
//                 _stprintf(tmp, L"Button %d", j + 1);
//                 did->buttonname[j] = _tcsdup(tmp);
//             }
//             did->priority = -1;
//             fixbuttons(did);
//             fixthings(did);
//             num_joystick++;
//         }
//     }
//     return 1;
// }
static void sortobjects(struct didata* did)
{
    int i, j;
    short tmpi;
    int tmpl;
    CString tmpc;

    for (i = 0; i < did->axles; i++)
    {
        for (j = i + 1; j < did->axles; j++)
        {
            if (did->axissort[i] > did->axissort[j])
            {
                tmpi = did->axismappings[i];
                did->axismappings[i] = did->axismappings[j];
                did->axismappings[j] = tmpi;
                tmpi = did->axissort[i];
                did->axissort[i] = did->axissort[j];
                did->axissort[j] = tmpi;
                tmpi = did->axistype[i];
                did->axistype[i] = did->axistype[j];
                did->axistype[j] = tmpi;
                tmpl = did->axismax[i];
                did->axismax[i] = did->axismax[j];
                did->axismax[j] = tmpl;
                tmpl = did->axismin[i];
                did->axismin[i] = did->axismin[j];
                did->axismin[j] = tmpl;
                tmpi = did->axisusagepage[i];
                did->axisusagepage[i] = did->axisusagepage[j];
                did->axisusagepage[j] = tmpi;
                tmpc = did->axisname[i];
                did->axisname[i] = did->axisname[j];
                did->axisname[j] = tmpc;
            }
        }
    }
    for (i = 0; i < did->buttons; i++)
    {
        for (j = i + 1; j < did->buttons; j++)
        {
            if (did->buttonsort[i] > did->buttonsort[j])
            {
                tmpi = did->buttonmappings[i];
                did->buttonmappings[i] = did->buttonmappings[j];
                did->buttonmappings[j] = tmpi;
                tmpi = did->buttonsort[i];
                did->buttonsort[i] = did->buttonsort[j];
                did->buttonsort[j] = tmpi;
                tmpc = did->buttonname[i];
                did->buttonname[i] = did->buttonname[j];
                did->buttonname[j] = tmpc;
                tmpi = did->buttonaxisparent[i];
                did->buttonaxisparent[i] = did->buttonaxisparent[j];
                did->buttonaxisparent[j] = tmpi;
                tmpi = did->buttonaxisparentdir[i];
                did->buttonaxisparentdir[i] = did->buttonaxisparentdir[j];
                did->buttonaxisparentdir[j] = tmpi;
            }
        }
    }

#ifdef DI_DEBUG
    if (did->axles + did->buttons > 0)
    {
        Logger::Write(L"%s:\n", did->name);
        if (did->connection == DIDC_DX)
            Logger::Write(L"PGUID=%s\n", outGUID(&did->pguid));
        for (i = 0; i < did->axles; i++)
        {
            Logger::Write(L"%02X %03d '%s' (%d, [%d - %d])\n",
                did->axismappings[i], did->axismappings[i], did->axisname[i], did->axissort[i], did->axismin[i], did->axismax[i]);
        }
        for (i = 0; i < did->buttons; i++)
        {
            Logger::Write(L"%02X %03d '%s' (%d)\n",
                did->buttonmappings[i], did->buttonmappings[i], did->buttonname[i], did->buttonsort[i]);
        }
    }
#endif
}

#define RDP_DEVICE1 L"\\??\\Root#"
#define RDP_DEVICE2 L"\\\\?\\Root#"

static int rdpdevice(TCHAR* buf)
{
    if (!_tcsncmp(RDP_DEVICE1, buf, _tcslen(RDP_DEVICE1)))
        return 1;
    if (!_tcsncmp(RDP_DEVICE2, buf, _tcslen(RDP_DEVICE2)))
        return 1;
    return 0;
}

static void rawinputfixname(const TCHAR* name, const TCHAR* friendlyname)
{
    int i, ii, j;
    TCHAR tmp[MAX_DPATH];

    _stprintf(tmp, L"\\\\?\\%s", name);
    for (i = 4; i < _tcslen(tmp); i++)
    {
        if (tmp[i] == '\\')
            tmp[i] = '#';
        tmp[i] = _totupper(tmp[i]);
    }
    for (ii = 0; ii < 2; ii++)
    {
        for (i = 0; i < (ii == 0 ? num_mouse : num_keyboard); i++)
        {
            struct didata* did = ii == 0 ? &di_mouse[i] : &di_keyboard[i];
            TCHAR tmp2[MAX_DPATH];
            if (!did->rawinput || did->fullname)
                continue;
            for (j = 0; j < _tcslen(did->configname); j++)
                tmp2[j] = _totupper(did->configname[j]);
            tmp2[j] = 0;
            if (_tcslen(tmp2) >= _tcslen(tmp) && !_tcsncmp(tmp2, tmp, _tcslen(tmp)))
            {
                did->name = "";
                did->sortname = "";
                if (did->vid > 0 && did->pid > 0)
                    _stprintf(tmp, L"%s [%04X/%04X]", friendlyname, did->vid, did->pid);
                else
                    _stprintf(tmp, L"%s", friendlyname);
                did->name = tmp;
                did->sortname = tmp;

                if (Logging::DIRECTINPUT_DEBUG)
                    Logger::Write(L"'%s' ('%s')\n", did->name, did->configname);
            }
        }
    }
}

static void rawinputfriendlynames()
{
    HDEVINFO di;
    int i, ii;
#if 0
    GUID guid;
    SP_DEVICE_INTERFACE_DATA data;
    HidD_GetHidGuid(&guid);
    di = SetupDiGetClassDevs(&guid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
    if (di != INVALID_HANDLE_VALUE)
    {
        data.cbSize = sizeof data;
        i = 0;
        while (SetupDiEnumDeviceInterfaces(di, nullptr, &guid, i, &data))
        {
            DWORD size;
            if (!SetupDiGetDeviceInterfaceDetail(di, &data, nullptr, 0, &size, nullptr) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
            {
                PSP_INTERFACE_DEVICE_DETAIL_DATA details = (PSP_INTERFACE_DEVICE_DETAIL_DATA)xmalloc(byte, size);
                details->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
                if (SetupDiGetDeviceInterfaceDetail(di, &data, details, size, nullptr, nullptr))
                {
                    Logger::Write(L"'%s'\n", details->DevicePath);
                    HANDLE h = CreateFile(details->DevicePath,
                        GENERIC_READ, FILE_SHARE_READ,
                        nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
                    if (h != INVALID_HANDLE_VALUE)
                    {
                        TCHAR buf[128];
                        HidD_GetProductString(h, buf, sizeof buf);
                        CloseHandle(h);
                    }
                }
                free(details);
            }
            i++;
        }
    }
#endif
    for (ii = 0; ii < 2; ii++)
    {
        di = SetupDiGetClassDevs(ii == 0 ? &GUID_DEVCLASS_MOUSE : &GUID_DEVCLASS_KEYBOARD, nullptr, nullptr, DIGCF_PRESENT);
        if (di != INVALID_HANDLE_VALUE)
        {
            SP_DEVINFO_DATA dd;
            dd.cbSize = sizeof dd;
            for (i = 0; SetupDiEnumDeviceInfo(di, i, &dd); i++)
            {
                TCHAR buf[MAX_DPATH];
                DWORD size = 0;
                if (SetupDiGetDeviceInstanceId(di, &dd, buf, sizeof buf, &size))
                {
                    TCHAR fname[MAX_DPATH];
                    DWORD dt;
                    fname[0] = 0;
                    size = 0;
                    if (!SetupDiGetDeviceRegistryProperty(di, &dd,
                            SPDRP_FRIENDLYNAME, &dt, (PBYTE)fname, sizeof fname, &size))
                    {
                        size = 0;
                        SetupDiGetDeviceRegistryProperty(di, &dd,
                            SPDRP_DEVICEDESC, &dt, (PBYTE)fname, sizeof fname, &size);
                    }
                    if (size > 0 && fname[0])
                        rawinputfixname(buf, fname);
                }
            }
            SetupDiDestroyDeviceInfoList(di);
        }
    }
}

/* static */ const TCHAR* rawkeyboardlabels[256] =
{
    L"ESCAPE",
    L"1", L"2", L"3", L"4", L"5", L"6", L"7", L"8", L"9", L"0",
    L"MINUS", L"EQUALS", L"BACK", L"TAB",
    L"Q", L"W", L"E", L"R", L"T", L"Y", L"U", L"I", L"O", L"P",
    L"LBRACKET", L"RBRACKET", L"RETURN", L"LCONTROL",
    L"A", L"S", L"D", L"F", L"G", L"H", L"J", L"K", L"L",
    L"SEMICOLON", L"APOSTROPHE", L"GRAVE", L"LSHIFT", L"BACKSLASH",
    L"Z", L"X", L"C", L"V", L"B", L"N", L"M",
    L"COMMA", L"PERIOD", L"SLASH", L"RSHIFT", L"MULTIPLY", L"LMENU", L"SPACE", L"CAPITAL",
    L"F1", L"F2", L"F3", L"F4", L"F5", L"F6", L"F7", L"F8", L"F9", L"F10",
    L"NUMLOCK", L"SCROLL", L"NUMPAD7", L"NUMPAD8", L"NUMPAD9", L"SUBTRACT",
    L"NUMPAD4", L"NUMPAD5", L"NUMPAD6", L"ADD", L"NUMPAD1", L"NUMPAD2", L"NUMPAD3", L"NUMPAD0",
    L"DECIMAL", nullptr, nullptr, L"OEM_102", L"F11", L"F12",
    nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    L"F13", L"F14", L"F15", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    L"NUMPADEQUALS", nullptr, nullptr,
    L"PREVTRACK", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    L"NEXTTRACK", nullptr, nullptr, L"NUMPADENTER", L"RCONTROL", nullptr, nullptr,
    L"MUTE", L"CALCULATOR", L"PLAYPAUSE", nullptr, L"MEDIASTOP",
    nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    L"VOLUMEDOWN", nullptr, L"VOLUMEUP", nullptr, L"WEBHOME", L"NUMPADCOMMA", nullptr,
    L"DIVIDE", nullptr, L"SYSRQ", L"RMENU",
    nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    L"PAUSE", nullptr, L"HOME", L"UP", L"PRIOR", nullptr, L"LEFT", nullptr, L"RIGHT", nullptr, L"END",
    L"DOWN", L"NEXT", L"INSERT", L"DELETE",
    nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    L"LWIN", L"RWIN", L"APPS", L"POWER", L"SLEEP",
    nullptr, nullptr, nullptr,
    L"WAKE", nullptr, L"WEBSEARCH", L"WEBFAVORITES", L"WEBREFRESH", L"WEBSTOP",
    L"WEBFORWARD", L"WEBBACK", L"MYCOMPUTER", L"MAIL", L"MEDIASELECT",
    L""
};

static void getvidpid2(const TCHAR* devname, int* id, const TCHAR* str)
{
    TCHAR* dv = _tcsdup(devname);
    for (int i = 0; i < _tcslen(dv); i++)
        dv[i] = _totupper(dv[i]);
    TCHAR* s = _tcsstr(dv, str);
    if (s)
    {
        int val = -1;
        _stscanf(s + _tcslen(str), L"%X", &val);
        *id = val;
    }
    free(dv);
}

static void getvidpid(const TCHAR* devname, int* vid, int* pid, int* mi)
{
    *vid = *pid = *mi = -1;
    getvidpid2(devname, vid, L"VID_");
    getvidpid2(devname, pid, L"PID_");
    getvidpid2(devname, mi, L"MI_");
}

static void addrkblabels(struct didata* did)
{
    for (int k = 0; k < 254; k++)
    {
        TCHAR tmp[100];
        tmp[0] = 0;
        if (rawkeyboardlabels[k] != nullptr && rawkeyboardlabels[k][0])
            _tcscpy(tmp, rawkeyboardlabels[k]);
        if (!tmp[0])
            _stprintf(tmp, L"KEY_%02X", k + 1);
        did->buttonname[k] = tmp;
        did->buttonmappings[k] = k + 1;
        did->buttonsort[k] = k + 1;
        did->buttons++;
    }
}

struct hiddesc
{
    int priority;
    int page, usage;
    CString name;
    int type;
};

static const struct hiddesc hidtable[] =
{
    { 0x30, 1, 0x30, L"X", 0 },
    { 0x31, 1, 0x31, L"Y", 0 },
    { 0x32, 1, 0x32, L"Z", 0 },
    { 0x33, 1, 0x33, L"RX", 0 },
    { 0x34, 1, 0x34, L"RY", 0 },
    { 0x35, 1, 0x35, L"RZ", 0 },
    { 0x36, 1, 0x36, L"Slider", AXISTYPE_SLIDER },
    { 0x37, 1, 0x37, L"Dial", AXISTYPE_DIAL },
    { 0x38, 1, 0x38, L"Wheel", 0 },
    { 0x39, 1, 0x39, L"Hat Switch", AXISTYPE_POV_X },
    { 0x90, 1, 0x90, L"D-pad Up", 0 },
    { 0x91, 1, 0x91, L"D-pad Down", 0 },
    { 0x92, 1, 0x92, L"D-pad Right", 0 },
    { 0x93, 1, 0x93, L"D-pad Left", 0 },
    { 0xbb, 2, 0xbb, L"Throttle", AXISTYPE_SLIDER },
    { 0xba, 2, 0xba, L"Rudder", 0 },
    { 0 }
};

static bool initialize_rawinput()
{
    RAWINPUTDEVICELIST* ridl = 0;
    UINT num = 500, vtmp;
    int gotnum, bufsize;
    int rnum_mouse, rnum_kb, rnum_hid, rnum_raw;
    TCHAR* bufp, * buf1, * buf2;
    int rmouse = 0, rkb = 0, rhid = 0;
    TCHAR tmp[100];

    bufsize = 10000 * sizeof(TCHAR);
    bufp = xmalloc(TCHAR, 2 * bufsize / sizeof(TCHAR));
    buf1 = bufp;
    buf2 = buf1 + 10000;

    register_rawinput(0);
    if (GetRawInputDeviceList(nullptr, &num, sizeof(RAWINPUTDEVICELIST)) != 0)
    {
        Logger::Write(L"RAWINPUT error %08X\n", GetLastError());
        goto error2;
    }
    if (Logging::DIRECTINPUT_DEBUG)
        Logger::Write(L"RAWINPUT: found %d devices\n", num);

    if (num <= 0)
        goto error2;
    ridl = xcalloc(RAWINPUTDEVICELIST, num);
    gotnum = GetRawInputDeviceList(ridl, &num, sizeof(RAWINPUTDEVICELIST));
    if (gotnum <= 0)
    {
        Logger::Write(L"RAWINPUT didn't find any devices\n");
        goto error2;
    }
    rnum_raw = rnum_mouse = rnum_kb = rnum_hid = 0;
    for (int rawcnt = 0; rawcnt < gotnum; rawcnt++)
    {
        int type = ridl[rawcnt].dwType;
        HANDLE h = ridl[rawcnt].hDevice;

        if (GetRawInputDeviceInfo(h, RIDI_DEVICENAME, nullptr, &vtmp) == 1)
            continue;
        if (vtmp >= bufsize)
            continue;
        if (GetRawInputDeviceInfo(h, RIDI_DEVICENAME, buf1, &vtmp) == -1)
            continue;
        if (rdpdevice(buf1))
            continue;
        if (type == RIM_TYPEMOUSE)
            rnum_mouse++;
        else if (type == RIM_TYPEKEYBOARD)
            rnum_kb++;
        else if (type == RIM_TYPEHID)
            rnum_hid++;
    }

    for (int rawcnt = 0; rawcnt < gotnum; rawcnt++)
    {
        HANDLE h = ridl[rawcnt].hDevice;
        int type = ridl[rawcnt].dwType;

        if (type == RIM_TYPEKEYBOARD || type == RIM_TYPEMOUSE || type == RIM_TYPEHID)
        {
            TCHAR prodname[128];
            struct didata* did;
            PRID_DEVICE_INFO rdi;
            int v, i, j;

            if (rawinput_decided)
            {
                //  must not enable rawinput later, even if rawinput capable device was plugged in
                if (type == RIM_TYPEKEYBOARD && !rawinput_enabled_keyboard)
                    continue;
                if (type == RIM_TYPEMOUSE && !rawinput_enabled_mouse)
                    continue;
                if (type == RIM_TYPEHID && !rawinput_enabled_hid)
                    continue;
            }
            if (type == RIM_TYPEKEYBOARD)
                did = di_keyboard;
            else if (type == RIM_TYPEMOUSE)
                did = di_mouse;
            else if (type == RIM_TYPEHID)
            {
                if (!rawinput_enabled_hid)
                    continue;
                did = di_joystick;
            }
            else
                continue;

            if (GetRawInputDeviceInfo(h, RIDI_DEVICENAME, nullptr, &vtmp) == -1)
                continue;
            if (vtmp >= bufsize)
                continue;
            if (GetRawInputDeviceInfo(h, RIDI_DEVICENAME, buf1, &vtmp) == -1)
                continue;

            rdi = (PRID_DEVICE_INFO)buf2;
            memset(rdi, 0, sizeof(RID_DEVICE_INFO));
            rdi->cbSize = sizeof(RID_DEVICE_INFO);
            if (GetRawInputDeviceInfo(h, RIDI_DEVICEINFO, nullptr, &vtmp) == -1)
                continue;
            if (vtmp >= bufsize)
                continue;
            if (GetRawInputDeviceInfo(h, RIDI_DEVICEINFO, buf2, &vtmp) == -1)
                continue;

            if (type == RIM_TYPEMOUSE)
            {
                if (rdpdevice(buf1))
                    continue;
                if (num_mouse >= MAX_INPUT_DEVICES - 1) /* leave space for Windows mouse */
                    continue;
                did += num_mouse;
                num_mouse++;
                rmouse++;
                v = rmouse;
            }
            else if (type == RIM_TYPEKEYBOARD)
            {
                if (rdpdevice(buf1))
                    continue;
                if (num_keyboard >= MAX_INPUT_DEVICES)
                    continue;
                did += num_keyboard;
                num_keyboard++;
                rkb++;
                v = rkb;
            }
            else if (type == RIM_TYPEHID)
            {
                if (rdpdevice(buf1))
                    continue;
                if (rdi->hid.usUsage != 4 && rdi->hid.usUsage != 5)
                {
                    continue;
                }
                if (num_joystick >= MAX_INPUT_DEVICES)
                    continue;
                did += num_joystick;
                num_joystick++;
                rhid++;
                v = rhid;
            }

            prodname[0] = 0;
            HANDLE hhid = CreateFile(buf1, 0, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
            if (hhid != INVALID_HANDLE_VALUE)
            {
                if (!HidD_GetProductString(hhid, prodname, sizeof prodname))
                {
                    prodname[0] = 0;
                }
                else
                {
                    while (_tcslen(prodname) > 0 && prodname[_tcslen(prodname) - 1] == ' ')
                        prodname[_tcslen(prodname) - 1] = 0;
                }
            }

            rnum_raw++;
            cleardid(did);
            getvidpid(buf1, &did->vid, &did->pid, &did->mi);
            if (prodname[0])
            {
                _tcscpy(tmp, prodname);
                did->fullname = true;
            }
            else
            {
                if (did->vid > 0 && did->pid > 0)
                    _stprintf(tmp, L"%s (%04X/%04X)", type == RIM_TYPEHID ? L"RAW HID" : (type == RIM_TYPEMOUSE ? L"RAW Mouse" : L"RAW Keyboard"), did->vid, did->pid);
                else
                    _stprintf(tmp, L"%s", type == RIM_TYPEHID ? L"RAW HID" : (type == RIM_TYPEMOUSE ? L"RAW Mouse" : L"RAW Keyboard"));
            }
            did->name = tmp;
            did->rawinput = h;
            did->connection = DIDC_RAW;

            if (Logging::DIRECTINPUT_DEBUG)
                Logger::Write(L"%p %s: ", h, type == RIM_TYPEHID ? L"hid" : (type == RIM_TYPEMOUSE ? L"mouse" : L"keyboard"));

            did->sortname = buf1;

            if (Logging::DIRECTINPUT_DEBUG)
                Logger::Write(L"'%s'\n", buf1);

            did->configname = buf1;

            if (type == RIM_TYPEMOUSE)
            {
                PRID_DEVICE_INFO_MOUSE rdim = &rdi->mouse;

                if (Logging::DIRECTINPUT_DEBUG)
                {
                    Logger::Write(L"id=%d buttons=%d hw=%d rate=%d\n",
                        rdim->dwId, rdim->dwNumberOfButtons, rdim->fHasHorizontalWheel, rdim->dwSampleRate);
                }
                if (rdim->dwNumberOfButtons >= MAX_MAPPINGS)
                {
                    Logger::Write(L"bogus number of buttons, ignored\n");
                }
                else
                {
                    did->buttons_real = did->buttons = rdim->dwNumberOfButtons;
                    for (j = 0; j < did->buttons; j++)
                    {
                        did->buttonsort[j] = j;
                        did->buttonmappings[j] = j;
                        _stprintf(tmp, L"Button %d", j + 1);
                        did->buttonname[j] = tmp;
                    }
                    did->axles = 3;
                    did->axissort[0] = 0;
                    did->axismappings[0] = 0;
                    did->axisname[0] = L"X-Axis";
                    did->axissort[1] = 1;
                    did->axismappings[1] = 1;
                    did->axisname[1] = L"Y-Axis";
                    did->axissort[2] = 2;
                    did->axismappings[2] = 2;
                    did->axisname[2] = L"Wheel";
                    addplusminus(did, 2);
                    if (1 || rdim->fHasHorizontalWheel)   // why is this always false?
                    {
                        did->axissort[3] = 3;
                        did->axismappings[3] = 3;
                        did->axisname[3] = L"HWheel";
                        did->axles++;
                        addplusminus(did, 3);
                    }
                    did->priority = -1;
                }
            }
            else if (type == RIM_TYPEKEYBOARD)
            {
                PRID_DEVICE_INFO_KEYBOARD rdik = &rdi->keyboard;

                if (Logging::DIRECTINPUT_DEBUG)
                {
                    Logger::Write(L"type=%d sub=%d mode=%d fkeys=%d indicators=%d tkeys=%d\n",
                        rdik->dwType, rdik->dwSubType, rdik->dwKeyboardMode,
                        rdik->dwNumberOfFunctionKeys, rdik->dwNumberOfIndicators, rdik->dwNumberOfKeysTotal);
                }
                addrkblabels(did);
            }
            else
            {
                bool ok = false;
                if (HidD_GetPreparsedData(hhid, &did->hidpreparseddata))
                {
                    if (HidP_GetCaps(did->hidpreparseddata, &did->hidcaps) == HIDP_STATUS_SUCCESS)
                    {
                        PHIDP_BUTTON_CAPS bcaps;
                        USHORT size = did->hidcaps.NumberInputButtonCaps;
                        bcaps = xmalloc(HIDP_BUTTON_CAPS, size);
                        if (HidP_GetButtonCaps(HidP_Input, bcaps, &size, did->hidpreparseddata) == HIDP_STATUS_SUCCESS)
                        {
                            int buttoncnt = 0;
                            for (i = 0; i < size && buttoncnt < MAX_MAPPINGS; i++)
                            {
                                int first, last;
                                if (bcaps[i].IsRange)
                                {
                                    first = bcaps[i].Range.UsageMin;
                                    last = bcaps[i].Range.UsageMax;
                                }
                                else
                                {
                                    first = last = bcaps[i].NotRange.Usage;
                                }
                                for (j = first; j <= last && buttoncnt < MAX_MAPPINGS; j++)
                                {
                                    did->buttonsort[buttoncnt] = j * 2;
                                    did->buttonmappings[buttoncnt] = j;
                                    _stprintf(tmp, L"Button %d", j);
                                    did->buttonname[buttoncnt] = _tcsdup(tmp);
                                    buttoncnt++;
                                }
                            }
                            if (buttoncnt > 0)
                            {
                                did->buttons = buttoncnt;
                                ok = true;
                            }
                        }
                        free(bcaps);
                        PHIDP_VALUE_CAPS vcaps;
                        size = did->hidcaps.NumberInputValueCaps;
                        vcaps = xmalloc(HIDP_VALUE_CAPS, size);
                        if (HidP_GetValueCaps(HidP_Input, vcaps, &size, did->hidpreparseddata) == HIDP_STATUS_SUCCESS)
                        {
                            int axiscnt = 0;
                            for (i = 0; i < size && axiscnt < MAX_MAPPINGS; i++)
                            {
                                int first, last;
                                if (vcaps[i].IsRange)
                                {
                                    first = vcaps[i].Range.UsageMin;
                                    last = vcaps[i].Range.UsageMax;
                                }
                                else
                                {
                                    first = last = vcaps[i].NotRange.Usage;
                                }
                                for (int acnt = first; acnt <= last && axiscnt < MAX_MAPPINGS; acnt++)
                                {
                                    int ht;
                                    for (ht = 0; hidtable[ht].name; ht++)
                                    {
                                        if (hidtable[ht].usage == acnt && hidtable[ht].page == vcaps[i].UsagePage)
                                        {
                                            if (hidtable[ht].page == 0x01 && acnt == 0x39)   // POV
                                            {
                                                if (axiscnt + 1 < MAX_MAPPINGS)
                                                {
                                                    for (int l = 0; l < 2; l++)
                                                    {
                                                        TCHAR tmp[256];
                                                        _stprintf(tmp, L"%s (%c)", hidtable[ht].name, l == 0 ? 'X' : 'Y');
                                                        did->axisname[axiscnt] = _tcsdup(tmp);
                                                        did->axissort[axiscnt] = hidtable[ht].priority * 2 + l;
                                                        did->axismappings[axiscnt] = acnt;
                                                        did->axisusagepage[axiscnt] = vcaps[i].UsagePage;
                                                        did->axismin[axiscnt] = vcaps[i].LogicalMin;
                                                        did->axismax[axiscnt] = vcaps[i].LogicalMax < 0 || vcaps[i].LogicalMax > 65535 ? 65535 : vcaps[i].LogicalMax;
                                                        did->axistype[axiscnt] = l + 1;
                                                        axiscnt++;
                                                    }
                                                }
                                            }
                                            else
                                            {
                                                did->axissort[axiscnt] = hidtable[ht].priority * 2;
                                                did->axisname[axiscnt] = _tcsdup(hidtable[ht].name);
                                                did->axismappings[axiscnt] = acnt;
                                                did->axisusagepage[axiscnt] = vcaps[i].UsagePage;
                                                did->axismin[axiscnt] = vcaps[i].LogicalMin;
                                                did->axismax[axiscnt] = vcaps[i].LogicalMax < 0 || vcaps[i].LogicalMax > 65535 ? 65535 : vcaps[i].LogicalMax;
                                                did->axistype[axiscnt] = hidtable[ht].type;
                                                axiscnt++;
                                            }
                                            break;
                                        }
                                    }
                                    if (hidtable[ht].name == "")
                                        Logger::Write(L"unsupported usage %d/%d\n", vcaps[i].UsagePage, acnt);
                                }
                            }
                            if (axiscnt > 0)
                            {
                                did->axles = axiscnt;
                                ok = true;
                            }
                        }
                        free(vcaps);
                    }
                }
                if (ok)
                {
                    did->hidbuffer = xmalloc(CHAR, did->hidcaps.InputReportByteLength + 1);
                    did->hidbufferprev = xmalloc(CHAR, did->hidcaps.InputReportByteLength + 1);
                    did->maxusagelistlength = HidP_MaxUsageListLength(HidP_Input, 0, did->hidpreparseddata);
                    did->usagelist = xmalloc(USAGE_AND_PAGE, did->maxusagelistlength);
                    did->prevusagelist = xcalloc(USAGE_AND_PAGE, did->maxusagelistlength);
                    fixbuttons(did);
                    fixthings(did);
                }
                else
                {
                    if (did->hidpreparseddata)
                        HidD_FreePreparsedData(did->hidpreparseddata);
                    did->hidpreparseddata = nullptr;
                    num_joystick--;
                    rhid--;
                }
            }
            if (hhid != INVALID_HANDLE_VALUE)
                CloseHandle(hhid);
        }
    }

    if (rnum_kb && num_keyboard < MAX_INPUT_DEVICES - 1)
    {
        struct didata* did = di_keyboard + num_keyboard;
        num_keyboard++;
        rnum_kb++;
        did->name = L"WinUAE null keyboard";
        did->rawinput = nullptr;
        did->connection = DIDC_RAW;
        did->sortname = L"NULLKEYBOARD";
        did->priority = -2;
        did->configname = L"NULLKEYBOARD";
        addrkblabels(did);
    }

    rawinputfriendlynames();

    free(ridl);
    free(bufp);
    if (rnum_raw > 0)
        rawinput_available = 1;

    for (int i = 0; i < num_mouse; i++)
        sortobjects(&di_mouse[i]);
    for (int i = 0; i < num_joystick; i++)
        sortobjects(&di_joystick[i]);

    return 1;

    // Logger::Write(L"RAWINPUT not available or failed to initialize\n");

    error2 :

    Logger::WriteLine(L"initialize_rawinput #error");

    free(ridl);
    free(bufp);
    return 0;
}

// static void initialize_windowsmouse()
// {
//     struct didata* did = di_mouse;
//     TCHAR tmp[100], * name;
//     int i, j;
//
//     did += num_mouse;
//     for (i = 0; i < 1; i++)
//     {
//         if (num_mouse >= MAX_INPUT_DEVICES)
//             return;
//         num_mouse++;
//         name = (i == 0) ? L"Windows mouse" : L"Mousehack mouse";
//         did->connection = DIDC_WIN;
//         did->name = i ? L"Mousehack mouse (Required for tablets)" : L"Windows mouse";
//         did->sortname = i ? L"Windowsmouse2" : L"Windowsmouse1";
//         did->configname = i ? L"WINMOUSE2" : L"WINMOUSE1";
//         did->buttons = GetSystemMetrics(SM_CMOUSEBUTTONS);
//         if (did->buttons < 3)
//             did->buttons = 3;
//         if (did->buttons > 5)
//             did->buttons = 5; /* no non-direcinput support for >5 buttons */
//         did->buttons_real = did->buttons;
//         for (j = 0; j < did->buttons; j++)
//         {
//             did->buttonsort[j] = j;
//             _stprintf(tmp, L"Button %d", j + 1);
//             did->buttonname[j] = tmp;
//         }
//         winmousewheelbuttonstart = did->buttons;
//         did->axles = os_vista ? 4 : 3;
//         did->axissort[0] = 0;
//         did->axisname[0] = L"X-Axis";
//         did->axissort[1] = 1;
//         did->axisname[1] = L"Y-Axis";
//         if (did->axles > 2)
//         {
//             did->axissort[2] = 2;
//             did->axisname[2] = L"Wheel";
//             addplusminus(did, 2);
//         }
//         if (did->axles > 3)
//         {
//             did->axissort[3] = 3;
//             did->axisname[3] = L"HWheel";
//             addplusminus(did, 3);
//         }
//         did->priority = 2;
//         did->wininput = i + 1;
//         did++;
//     }
// }

static byte rawkeystate[256];

static void handle_rawinput_2(RAWINPUT* raw)
{
    int i, num;
    struct didata* did = nullptr;
    int istest = inputdevice_istest();

    if (raw->header.dwType == RIM_TYPEMOUSE)
    {
        PRAWMOUSE rm = &raw->data.mouse;
        HANDLE h = raw->header.hDevice;

        for (num = 0; num < num_mouse; num++)
        {
            did = &di_mouse[num];
            if (did->rawinput == h)
                break;
        }
        if (Logging::DIRECTINPUT_RAW_DEBUG > 1)
        {
            Logger::Write(L"HANDLE=%08x %04x %04x %04x %08x %3d %3d %08x M=%d\n",
                raw->header.hDevice,
                rm->usFlags,
                rm->usButtonFlags,
                rm->usButtonData,
                rm->ulRawButtons,
                rm->lLastX,
                rm->lLastY,
                rm->ulExtraInformation, num < num_mouse ? num + 1 : -1);
        }

        if (num == num_mouse)
            return;

        if (isfocus() > 0 || istest)
        {
            static int lastx[MAX_INPUT_DEVICES], lasty[MAX_INPUT_DEVICES];
            static int lastmbr[MAX_INPUT_DEVICES], lastmb[MAX_INPUT_DEVICES];
            for (i = 0; i < (5 > did->buttons ? did->buttons : 5); i++)
            {
                if (rm->usButtonFlags & (3 << (i * 2)))
                {
                    int state = (rm->usButtonFlags & (1 << (i * 2))) ? 1 : 0;
                    setmousebuttonstate(num, i, state);
                    lastmb[num] &= ~(1 << i);
                    if (state)
                        lastmb[num] |= 1 << i;
                }
            }
            if (did->buttons > 5)
            {
                for (i = 5; i < did->buttons; i++)
                {
                    if ((lastmbr[num] & (1 << i)) != (rm->ulRawButtons & (1 << i)))
                        setmousebuttonstate(num, i, (rm->ulRawButtons & (1 << i)) ? 1 : 0);
                }
                lastmbr[num] = rm->ulRawButtons;
            }
            for (i = 0; i < 2; i++)
            {
                int bnum = did->buttons_real + (i * 2);
                //  RI_MOUSE_WHEEL << 1 = HWHEEL
                if (rm->usButtonFlags & (RI_MOUSE_WHEEL << i))
                {
                    int val = (short)rm->usButtonData;
                    setmousestate(num, 2, val, 0);
                    if (istest)
                        setmousestate(num, 2, 0, 0);
                    if (val < 0)
                        setmousebuttonstate(num, bnum + 0, -1);
                    else if (val > 0)
                        setmousebuttonstate(num, bnum + 1, -1);
                }
            }
            if (istest)
            {
                if (abs(rm->lLastX - lastx[num]) > 7)
                {
                    setmousestate(num, 0, rm->lLastX, (rm->usFlags & (MOUSE_MOVE_ABSOLUTE | MOUSE_VIRTUAL_DESKTOP)) ? 1 : 0);
                    lastx[num] = rm->lLastX;
                    lasty[num] = rm->lLastY;
                }
                else if (abs(rm->lLastY - lasty[num]) > 7)
                {
                    setmousestate(num, 1, rm->lLastY, (rm->usFlags & (MOUSE_MOVE_ABSOLUTE | MOUSE_VIRTUAL_DESKTOP)) ? 1 : 0);
                    lastx[num] = rm->lLastX;
                    lasty[num] = rm->lLastY;
                }
            }
            else
            {
                if (rm->lLastX != lastx[num])
                    setmousestate(num, 0, rm->lLastX, (rm->usFlags & (MOUSE_MOVE_ABSOLUTE | MOUSE_VIRTUAL_DESKTOP)) ? 1 : 0);
                if (rm->lLastY != lasty[num])
                    setmousestate(num, 1, rm->lLastY, (rm->usFlags & (MOUSE_MOVE_ABSOLUTE | MOUSE_VIRTUAL_DESKTOP)) ? 1 : 0);
                lastx[num] = rm->lLastX;
                lasty[num] = rm->lLastY;
            }
        }
        if (isfocus() && !istest)
        {
            if (did->buttons >= 3 && (rm->usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN))
            {
                if (g_curr_conf.win32_middle_mouse)
                {
                    if (isfullscreen() > 0)
                        minimizewindow();
                    if (mouseactive)
                        setmouseactive(0);
                }
            }
        }
    }
    else if (raw->header.dwType == RIM_TYPEHID)
    {
        int k, j;
        PRAWHID hid = &raw->data.hid;
        HANDLE h = raw->header.hDevice;
        PCHAR rawdata;
#if 0
        byte* r = hid->bRawData;
        Logger::Write(L"%d %d %02x%02x%02x%02x%02x%02x%02x\n", hid->dwCount, hid->dwSizeHid,
            r[0], r[1], r[2], r[3], r[4], r[5], r[6]);
#endif
        for (num = 0; num < num_joystick; num++)
        {
            did = &di_joystick[num];
            if (did->connection != DIDC_RAW)
                continue;
            if (did->acquired)
            {
                if (did->rawinput == h)
                    break;
            }
        }

        if (num < num_joystick)
        {
            rawdata = (PCHAR)hid->bRawData;
            for (int i = 0; i < hid->dwCount; i++)
            {
                DWORD usagelength = did->maxusagelistlength;
                if (HidP_GetUsagesEx(HidP_Input, 0, did->usagelist, &usagelength, did->hidpreparseddata, rawdata, hid->dwSizeHid) == HIDP_STATUS_SUCCESS)
                {
                    for (k = 0; k < usagelength; k++)
                    {
                        for (j = 0; j < did->maxusagelistlength; j++)
                        {
                            if (did->usagelist[k].UsagePage == did->prevusagelist[j].UsagePage &&
                                did->usagelist[k].Usage == did->prevusagelist[j].Usage)
                                break;
                        }
                        if (j == did->maxusagelistlength || did->prevusagelist[j].Usage == 0)
                        {
                            //Logger::Write (L"%d/%d ON\n", did->usagelist[k].UsagePage, did->usagelist[k].Usage);
                            for (int l = 0; l < did->buttons; l++)
                            {
                                if (did->buttonmappings[l] == did->usagelist[k].Usage)
                                    setjoybuttonstate(num, l, 1);
                            }
                        }
                        else
                        {
                            did->prevusagelist[j].Usage = 0;
                        }
                    }
                    for (j = 0; j < did->maxusagelistlength; j++)
                    {
                        if (did->prevusagelist[j].Usage)
                        {
                            //Logger::Write (L"%d/%d OFF\n", did->prevusagelist[j].UsagePage, did->prevusagelist[j].Usage);
                            for (int l = 0; l < did->buttons; l++)
                            {
                                if (did->buttonmappings[l] == did->prevusagelist[k].Usage)
                                    setjoybuttonstate(num, l, 0);
                            }
                        }
                    }
                    memcpy(did->prevusagelist, did->usagelist, usagelength * sizeof USAGE_AND_PAGE);
                    memset(did->prevusagelist + usagelength, 0, (did->maxusagelistlength - usagelength) * sizeof USAGE_AND_PAGE);
                    for (int k = 0; k < did->axles; k++)
                    {
                        ULONG val;
                        int usage = did->axismappings[k];
                        if (HidP_GetUsageValue(HidP_Input, did->axisusagepage[k], 0, usage, &val, did->hidpreparseddata, rawdata, hid->dwSizeHid) == HIDP_STATUS_SUCCESS)
                        {
                            int data = 0;
                            int range = 0;
                            int digitalrange = 0;
                            int axisnum = k;
                            int type = did->axistype[k];
                            if (type == AXISTYPE_POV_X || type == AXISTYPE_POV_Y)
                            {
                                int min = did->axismin[k];
                                range = 127;
                                if (did->axismax[k] - min == 7)
                                {
                                    if (val == min + 0 && type == AXISTYPE_POV_Y)
                                        data = -127;
                                    if (val == min + 2 && type == AXISTYPE_POV_X)
                                        data = 127;
                                    if (val == min + 4 && type == AXISTYPE_POV_Y)
                                        data = 127;
                                    if (val == min + 6 && type == AXISTYPE_POV_X)
                                        data = -127;
                                }
                                else
                                {
                                    if (val == min + 0 && type == AXISTYPE_POV_Y)
                                        data = -127;
                                    if (val == min + 1 && type == AXISTYPE_POV_X)
                                        data = 127;
                                    if (val == min + 2 && type == AXISTYPE_POV_Y)
                                        data = 127;
                                    if (val == min + 3 && type == AXISTYPE_POV_X)
                                        data = -127;
                                }
                            }
                            else if (type == AXISTYPE_SLIDER || type == AXISTYPE_DIAL)
                            {
                                range = did->axismax[k] - did->axismin[k];
                                data = val - did->axismin[k];
                                range = 0;
                                data = 0;
                            }
                            else
                            {
                                range = (did->axismax[k] - did->axismin[k] + 1) / 2;
                                digitalrange = range / 2;
                                data = (val - did->axismin[k]) - range;
                                if (istest)
                                {
                                    if (data < -digitalrange)
                                        data = -range;
                                    else if (data > digitalrange)
                                        data = range;
                                    else
                                        data = 0;
                                }
                            }
                            if (data != axisold[num][axisnum] && range)
                            {
                                //Logger::Write (L"%d %d: %d\n", num, axisnum, data);
                                axisold[num][axisnum] = data;
                                if (type == 0)
                                {
                                    int bstate = -1;
                                    for (j = 0; j < did->buttons; j++)
                                    {
                                        if (did->buttonaxisparent[j] >= 0 && did->buttonmappings[j] == usage)
                                        {
                                            if (did->buttonaxisparentdir[j] == 0 && data > digitalrange)
                                                bstate = j;
                                            else if (did->buttonaxisparentdir[j] && data < -digitalrange)
                                                bstate = j;
                                        }
                                    }
                                    if (bstate >= 0)
                                        setjoybuttonstate(num, bstate, -1);
                                }
                                setjoystickstate(num, axisnum, data, range);
                            }
                        }
                    }
                }
                rawdata += hid->dwSizeHid;
            }
        }
    }
    else if (raw->header.dwType == RIM_TYPEKEYBOARD)
    {
        PRAWKEYBOARD rk = &raw->data.keyboard;
        HANDLE h = raw->header.hDevice;
        int scancode = rk->MakeCode & 0x7f;
        int pressed = (rk->Flags & RI_KEY_BREAK) ? 0 : 1;

        if (Logging::DIRECTINPUT_RAW_DEBUG > 1)
        {
            Logger::Write(L"HANDLE=%x CODE=%x Flags=%x VK=%x MSG=%x EXTRA=%x SC=%x\n",
                raw->header.hDevice,
                rk->MakeCode,
                rk->Flags,
                rk->VKey,
                rk->Message,
                rk->ExtraInformation,
                scancode);
        }
        //  eat E1 extended keys
        if (rk->Flags & (RI_KEY_E1))
            return;
        if (scancode == 0)
        {
            scancode = MapVirtualKey(rk->VKey, MAPVK_VK_TO_VSC);
            if (Logging::DIRECTINPUT_RAW_DEBUG > 1)
                Logger::Write(L"VK->CODE: %x\n", scancode);
        }
        if (rk->VKey == 0xff || (rk->Flags & RI_KEY_E0))
            scancode |= 0x80;
        if (rk->MakeCode == KEYBOARD_OVERRUN_MAKE_CODE)
            return;
        if (scancode == 0xaa || scancode == 0)
            return;

        if (!istest)
        {
            if (scancode == DIK_SYSRQ)
                clipboard_disable(!!pressed);
            if (h == nullptr)
            {
                //  swallow led key fake messages
                if (g_curr_conf.keyboard_leds[KBLED_NUMLOCKB] > 0 && scancode == DIK_NUMLOCK)
                    return;
                if (g_curr_conf.keyboard_leds[KBLED_CAPSLOCKB] > 0 && scancode == DIK_CAPITAL)
                    return;
                if (g_curr_conf.keyboard_leds[KBLED_SCROLLLOCKB] > 0 && scancode == DIK_SCROLL)
                    return;
            }
            else
            {
                static bool ch;
                if (pressed)
                {
                    if (g_curr_conf.keyboard_leds[KBLED_NUMLOCKB] > 0 && scancode == DIK_NUMLOCK)
                    {
                        oldleds ^= KBLED_NUMLOCKM;
                        ch = true;
                    }
                    if (g_curr_conf.keyboard_leds[KBLED_CAPSLOCKB] > 0 && scancode == DIK_CAPITAL)
                    {
                        oldleds ^= KBLED_CAPSLOCKM;
                        ch = true;
                    }
                    if (g_curr_conf.keyboard_leds[KBLED_SCROLLLOCKB] > 0 && scancode == DIK_SCROLL)
                    {
                        oldleds ^= KBLED_SCROLLLOCKM;
                        ch = true;
                    }
                }
                else if (ch && isfocus())
                {
                    ch = false;
                    set_leds(newleds);
                }
            }
        }

        for (num = 0; num < num_keyboard; num++)
        {
            did = &di_keyboard[num];
            if (did->connection != DIDC_RAW)
                continue;
            if (did->acquired)
            {
                if (did->rawinput == h)
                    break;
                if (h == nullptr && num_keyboard == 1)
                    break;
            }
        }
        if (num == num_keyboard)
        {
            for (num = 0; num < num_keyboard; num++)
            {
                if (did->connection == DIDC_RAW && did->acquired && did->rawinput == nullptr)
                    break;
            }
            if (num == num_keyboard)
            {
                if (!istest && scancode == DIK_F12 && pressed && isfocus())
                    inputdevice_add_inputcode(AKS_ENTERGUI, 1);
                return;
            }
        }

        if (rawkeystate[scancode] == pressed)
        {
            return;
        }
        rawkeystate[scancode] = pressed;
        if (istest)
        {
            if (pressed && (scancode == DIK_F12 || scancode == DIK_F11))
                return;
            if (scancode == DIK_F12)
                scancode = -1;
            if (scancode == DIK_F11)
            {
                inputdevice_testrecord(IDTYPE_KEYBOARD, num, IDEV_WIDGET_BUTTON, 0x100, 1);
                inputdevice_testrecord(IDTYPE_KEYBOARD, num, IDEV_WIDGET_BUTTON, 0x100, 0);
            }
            else
            {
                inputdevice_testrecord(IDTYPE_KEYBOARD, num, IDEV_WIDGET_BUTTON, scancode, pressed);
            }
        }
        else
        {
            scancode = keyhack(scancode, pressed, num);
            if (scancode < 0 || isfocus() == 0)
                return;
            di_keycodes[num][scancode] = pressed;
            if (stopoutput == 0)
                my_kbd_handler(num, scancode, pressed);
        }
    }
}

void handle_rawinput(LPARAM lParam)
{
    UINT dwSize;
    BYTE lpb[1000];
    RAWINPUT* raw;

    if (!rawinput_available)
        return;
    GetRawInputData((HRAWINPUT)lParam, RID_INPUT, nullptr, &dwSize, sizeof(RAWINPUTHEADER));
    if (dwSize <= sizeof(lpb))
    {
        if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) == dwSize)
        {
            raw = (RAWINPUT*)lpb;
            handle_rawinput_2(raw);
            DefRawInputProc(&raw, 1, sizeof(RAWINPUTHEADER));
        }
    }
}

static void unacquire(LPDIRECTINPUTDEVICE8 lpdi, TCHAR* txt)
{
    HRESULT hr;
    if (lpdi)
    {
        hr = IDirectInputDevice8_Unacquire(lpdi);
        if (FAILED(hr) && hr != DI_NOEFFECT)
            Logger::Write(L"unacquire %s failed, %s\n", txt, DXError(hr));
    }
}

static int acquire(LPDIRECTINPUTDEVICE8 lpdi, TCHAR* txt)
{
    HRESULT hr = DI_OK;
    if (lpdi)
    {
        hr = IDirectInputDevice8_Acquire(lpdi);
        if (FAILED(hr) && hr != 0x80070005)
        {
            Logger::Write(L"acquire %s failed, %s\n", txt, DXError(hr));
        }
    }
    return SUCCEEDED(hr) ? 1 : 0;
}

static int setcoop(struct didata* did, DWORD mode, TCHAR* txt)
{
    HRESULT hr = DI_OK;
    HWND hwnd;
    int test = inputdevice_istest();

    if (!test)
        hwnd = hMainWnd;
    else
        hwnd = hGUIWnd;

    if (did->lpdi)
    {
        did->coop = 0;
        if (!did->coop && hwnd)
        {
            hr = IDirectInputDevice8_SetCooperativeLevel(did->lpdi, hwnd, mode);
            if (FAILED(hr) && hr != E_NOTIMPL)
            {
                Logger::Write(L"setcooperativelevel %s failed, %s\n", txt, DXError(hr));
            }
            else
            {
                did->coop = 1;
                // Logger::Write (L"cooperativelevel %s set\n", txt);
            }
        }
    }
    return SUCCEEDED(hr) ? 1 : 0;
}

static void sortdd(struct didata* dd, int num, int type)
{
    int i, j;
    struct didata ddtmp;

    for (i = 0; i < num; i++)
    {
        dd[i].type = type;

        for (j = i + 1; j < num; j++)
        {
            dd[j].type = type;

            if (dd[i].priority < dd[j].priority || (dd[i].priority == dd[j].priority && _tcscmp(dd[i].sortname, dd[j].sortname) > 0))
            {
                ddtmp = dd[i];
                dd[i] = dd[j];
                dd[j] = ddtmp;
            }
        }
    }

    /* rename duplicate names */
    for (i = 0; i < num; i++)
    {
        for (j = i + 1; j < num; j++)
        {
            if (!_tcscmp(dd[i].name, dd[j].name))
            {
                int cnt = 1;
                TCHAR tmp[MAX_DPATH], tmp2[MAX_DPATH];
                _tcscpy(tmp2, dd[i].name);
                for (j = i; j < num; j++)
                {
                    if (!_tcscmp(tmp2, dd[j].name))
                    {
                        _stprintf(tmp, L"%s [%d]", dd[j].name, cnt++);
                        dd[j].name = tmp;
                    }
                }
                break;
            }
        }
    }
}

static int isg(const GUID* g, const GUID* g2, short* dwofs, int v)
{
    if (!memcmp(g, g2, sizeof(GUID)))
    {
        *dwofs = v;
        return 1;
    }
    return 0;
}

static int makesort_joy(const GUID* g, short* dwofs)
{
    if (isg(g, &GUID_XAxis, dwofs, DIJOFS_X))
        return -99;
    if (isg(g, &GUID_YAxis, dwofs, DIJOFS_Y))
        return -98;
    if (isg(g, &GUID_ZAxis, dwofs, DIJOFS_Z))
        return -97;
    if (isg(g, &GUID_RxAxis, dwofs, DIJOFS_RX))
        return -89;
    if (isg(g, &GUID_RyAxis, dwofs, DIJOFS_RY))
        return -88;
    if (isg(g, &GUID_RzAxis, dwofs, DIJOFS_RZ))
        return -87;
    if (isg(g, &GUID_Slider, dwofs, DIJOFS_SLIDER(0)))
        return -79;
    if (isg(g, &GUID_Slider, dwofs, DIJOFS_SLIDER(1)))
        return -78;
    if (isg(g, &GUID_POV, dwofs, DIJOFS_POV(0)))
        return -69;
    if (isg(g, &GUID_POV, dwofs, DIJOFS_POV(1)))
        return -68;
    if (isg(g, &GUID_POV, dwofs, DIJOFS_POV(2)))
        return -67;
    if (isg(g, &GUID_POV, dwofs, DIJOFS_POV(3)))
        return -66;
    return *dwofs;
}

static int makesort_mouse(const GUID* g, short* dwofs)
{
    if (isg(g, &GUID_XAxis, dwofs, DIMOFS_X))
        return -99;
    if (isg(g, &GUID_YAxis, dwofs, DIMOFS_Y))
        return -98;
    if (isg(g, &GUID_ZAxis, dwofs, DIMOFS_Z))
        return -97;
    return *dwofs;
}

static BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext)
{
    struct didata* did = (struct didata*)pContext;
    int i;
    TCHAR tmp[100];

#if 0
    if (pdidoi->dwOfs != DIDFT_GETINSTANCE(pdidoi->dwType))
        Logger::Write(L"%x-%s: %x <> %x\n", pdidoi->dwType & 0xff, pdidoi->tszName,
            pdidoi->dwOfs, DIDFT_GETINSTANCE(pdidoi->dwType));
#endif
    if (pdidoi->dwType & DIDFT_AXIS)
    {
        int sort = 0;
        if (did->axles >= MAX_MAPPINGS)
            return DIENUM_CONTINUE;
        did->axismappings[did->axles] = DIDFT_GETINSTANCE(pdidoi->dwType);
        did->axisname[did->axles] = pdidoi->tszName;
        if (did->type == DID_JOYSTICK)
            sort = makesort_joy(&pdidoi->guidType, &did->axismappings[did->axles]);
        else if (did->type == DID_MOUSE)
            sort = makesort_mouse(&pdidoi->guidType, &did->axismappings[did->axles]);
        if (sort < 0)
        {
            for (i = 0; i < did->axles; i++)
            {
                if (did->axissort[i] == sort)
                {
                    Logger::Write(L"ignored duplicate '%s'\n", pdidoi->tszName);
                    return DIENUM_CONTINUE;
                }
            }
        }
        did->axissort[did->axles] = sort;
        did->axles++;
    }
    if (pdidoi->dwType & DIDFT_POV)
    {
        int numpov = 0;
        if (did->axles + 1 >= MAX_MAPPINGS)
            return DIENUM_CONTINUE;
        for (i = 0; i < did->axles; i++)
        {
            if (did->axistype[i])
            {
                numpov++;
                i++;
            }
        }
        if (did->type == DID_JOYSTICK)
            did->axissort[did->axles] = makesort_joy(&pdidoi->guidType, &did->axismappings[did->axles]);
        else if (did->type == DID_MOUSE)
            did->axissort[did->axles] = makesort_mouse(&pdidoi->guidType, &did->axismappings[did->axles]);
        for (i = 0; i < 2; i++)
        {
            did->axismappings[did->axles + i] = DIJOFS_POV(numpov);
            _stprintf(tmp, L"%s (%c)", pdidoi->tszName, i ? 'Y' : 'X');
            did->axisname[did->axles + i] = tmp;
            did->axissort[did->axles + i] = did->axissort[did->axles];
            did->axistype[did->axles + i] = i + 1;
        }
        did->axles += 2;
    }

    if (pdidoi->dwType & DIDFT_BUTTON)
    {
        if (did->buttons >= MAX_MAPPINGS)
            return DIENUM_CONTINUE;
        // const TCHAR* bname = did->buttonname[did->buttons] = pdidoi->tszName;
        if (did->type == DID_JOYSTICK)
        {
            // did->buttonmappings[did->buttons] = DIJOFS_BUTTON(DIDFT_GETINSTANCE (pdidoi->dwType));
            did->buttonmappings[did->buttons] = DIJOFS_BUTTON(did->buttons);
            did->buttonsort[did->buttons] = makesort_joy(&pdidoi->guidType, &did->buttonmappings[did->buttons]);
        }
        else if (did->type == DID_MOUSE)
        {
            // did->buttonmappings[did->buttons] = FIELD_OFFSET(DIMOUSESTATE2, rgbButtons) + DIDFT_GETINSTANCE (pdidoi->dwType);
            did->buttonmappings[did->buttons] = FIELD_OFFSET(DIMOUSESTATE2, rgbButtons) + did->buttons;
            did->buttonsort[did->buttons] = makesort_mouse(&pdidoi->guidType, &did->buttonmappings[did->buttons]);
        }
        else if (did->type == DID_KEYBOARD)
        {
            // did->buttonmappings[did->buttons] = pdidoi->dwOfs;
            did->buttonmappings[did->buttons] = DIDFT_GETINSTANCE(pdidoi->dwType);
        }
        did->buttons++;
    }

    return DIENUM_CONTINUE;
}

static void trimws(TCHAR* s)
{
    /* Delete trailing whitespace.  */
    int len = _tcslen(s);
    while (len > 0 && _tcscspn(s + len - 1, L"\t \r\n") == 0)
        s[--len] = '\0';
}

static void trimws(CString* s)
{
    trimws(s->GetBuffer());
    s->ReleaseBuffer();

    /* Delete trailing whitespace.  */
    int len = s->GetLength();
    while ((len > 0) && (s->Find(L"\t \r\n", len - 1) == 0))
        s[--len] = '\0';
}

static BOOL di_enumcallback2(LPCDIDEVICEINSTANCE lpddi, int joy)
{
    struct didata* did;
    int /*len,*/ type;
    TCHAR* typetxt;
    TCHAR tmp[100];

    type = lpddi->dwDevType & 0xff;
    // if (type == DI8DEVTYPE_MOUSE || type == DI8DEVTYPE_SCREENPOINTER)
    // {
    //     did = di_mouse;
    //     typetxt = L"Mouse";
    // }
    // else
    if ((type == DI8DEVTYPE_GAMEPAD || type == DI8DEVTYPE_JOYSTICK || type == DI8DEVTYPE_SUPPLEMENTAL ||
            type == DI8DEVTYPE_FLIGHT || type == DI8DEVTYPE_DRIVING || type == DI8DEVTYPE_1STPERSON) || joy)
    {
        did = di_joystick;
        typetxt = L"Game controller";
    }
    else if (type == DI8DEVTYPE_KEYBOARD)
    {
        did = di_keyboard;
        typetxt = L"Keyboard";
    }
    else
    {
        did = nullptr;
        typetxt = L"Unknown";
    }

    if (Logging::DIRECTINPUT_DEBUG)
    {
        Logger::Write(L"I=%s ", outGUID(&lpddi->guidInstance));
        Logger::Write(L"P=%s\n", outGUID(&lpddi->guidProduct));
        Logger::Write(L"'%s' '%s' %08X [%s]\n", lpddi->tszProductName, lpddi->tszInstanceName, lpddi->dwDevType, typetxt);
    }

    // if (did == di_mouse)
    // {
    //     if (num_mouse >= MAX_INPUT_DEVICES)
    //         return DIENUM_CONTINUE;
    //     did += num_mouse;
    //     num_mouse++;
    // }
    // else
    if (did == di_joystick)
    {
        if (num_joystick >= MAX_INPUT_DEVICES)
            return DIENUM_CONTINUE;
        did += num_joystick;
        num_joystick++;
    }
    else if (did == di_keyboard)
    {
        if (num_keyboard >= MAX_INPUT_DEVICES)
            return DIENUM_CONTINUE;
        did += num_keyboard;
        num_keyboard++;
    }
    else
        return DIENUM_CONTINUE;

    cleardid(did);
    if (lpddi->tszInstanceName)
    {
        did->name = lpddi->tszInstanceName;
    }
    else
    {
        did->name = L"[no name]";
    }
    trimws(&did->name);
    _stprintf(tmp, L"%08X-%04X-%04X-%02X%02X%02X%02X%02X%02X%02X%02X %08X-%04X-%04X-%02X%02X%02X%02X%02X%02X%02X%02X",
        lpddi->guidProduct.Data1, lpddi->guidProduct.Data2, lpddi->guidProduct.Data3,
        lpddi->guidProduct.Data4[0], lpddi->guidProduct.Data4[1], lpddi->guidProduct.Data4[2], lpddi->guidProduct.Data4[3],
        lpddi->guidProduct.Data4[4], lpddi->guidProduct.Data4[5], lpddi->guidProduct.Data4[6], lpddi->guidProduct.Data4[7],
        lpddi->guidInstance.Data1, lpddi->guidInstance.Data2, lpddi->guidInstance.Data3,
        lpddi->guidInstance.Data4[0], lpddi->guidInstance.Data4[1], lpddi->guidInstance.Data4[2], lpddi->guidInstance.Data4[3],
        lpddi->guidInstance.Data4[4], lpddi->guidInstance.Data4[5], lpddi->guidInstance.Data4[6], lpddi->guidInstance.Data4[7]);
    did->configname = tmp;
    trimws(&did->configname);
    did->iguid = lpddi->guidInstance;
    did->pguid = lpddi->guidProduct;
    did->sortname = did->name;
    did->connection = DIDC_DX;

    if (!memcmp(&did->iguid, &GUID_SysKeyboard, sizeof(GUID)) || !memcmp(&did->iguid, &GUID_SysMouse, sizeof(GUID)))
    {
        did->priority = 2;
        did->superdevice = 1;
    }
    return DIENUM_CONTINUE;
}

static BOOL CALLBACK di_enumcallback(LPCDIDEVICEINSTANCE lpddi, LPVOID dd)
{
    return di_enumcallback2(lpddi, 0);
}

static BOOL CALLBACK di_enumcallbackj(LPCDIDEVICEINSTANCE lpddi, LPVOID dd)
{
    return di_enumcallback2(lpddi, 1);
}

static void di_dev_free(struct didata* did)
{
    if (did->lpdi)
        IDirectInputDevice8_Release(did->lpdi);
    if (did->parjoy != INVALID_HANDLE_VALUE)
        CloseHandle(did->parjoy);

    // free(did->name);
    // free(did->sortname);
    // free(did->configname);
    // if (did->hidpreparseddata)
    //     HidD_FreePreparsedData(did->hidpreparseddata);
    // free(did->hidbuffer);
    // free(did->hidbufferprev);
    // free(did->usagelist);
    // free(did->prevusagelist);
    did->zeroes();
    // did->parjoy = INVALID_HANDLE_VALUE;
}

static int di_do_init()
{
    HRESULT hr;
    int i;

    num_mouse = num_joystick = num_keyboard = 0;

    for (i = 0; i < MAX_INPUT_DEVICES; i++)
    {
        di_dev_free(&di_joystick[i]);
        di_dev_free(&di_mouse[i]);
        di_dev_free(&di_keyboard[i]);
    }
    if (!no_rawinput)
    {
        if (Logging::DIRECTINPUT_DEBUG)
            Logger::Write(L"RawInput enumeration..\n");
        if (!initialize_rawinput())
            rawinput_enabled_hid = false;
    }
    else
    {
        rawinput_enabled_hid = false;
    }

    if (!rawinput_decided)
    {
        if (num_mouse > 0 && !test_rawinput(2))
            num_mouse = 0;
        if (num_keyboard > 0 && !test_rawinput(6))
            num_keyboard = 0;
        rawinput_enabled_keyboard = num_keyboard > 0;
        rawinput_enabled_mouse = num_mouse > 0;
        rawinput_decided = true;
    }

    if (!rawinput_enabled_hid)
    {
        hr = DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&g_lpdi, nullptr);
        if (FAILED(hr))
        {
            Logger::Write(L"DirectInput8Create failed, %s\n", DXError(hr));
        }
        else
        {
            if (dinput_enum_all)
            {
                Logger::Write(L"DirectInput enumeration..\n");
                g_lpdi->EnumDevices(DI8DEVCLASS_ALL, di_enumcallback, 0, DIEDFL_ATTACHEDONLY);
            }
            else
            {
                if (!rawinput_enabled_keyboard)
                {
                    Logger::Write(L"DirectInput enumeration.. Keyboards..\n");
                    g_lpdi->EnumDevices(DI8DEVCLASS_KEYBOARD, di_enumcallback, 0, DIEDFL_ATTACHEDONLY);
                }
                // if (!rawinput_enabled_mouse)
                // {
                //     Logger::Write(L"DirectInput enumeration.. Pointing devices..\n");
                //     g_lpdi->EnumDevices(DI8DEVCLASS_POINTER, di_enumcallback, 0, DIEDFL_ATTACHEDONLY);
                // }

                if (Logging::DIRECTINPUT_DEBUG)
                    Logger::Write(L"DirectInput enumeration.. Game controllers..\n");

                g_lpdi->EnumDevices(DI8DEVCLASS_GAMECTRL, di_enumcallbackj, 0, DIEDFL_ATTACHEDONLY);
            }
        }
    }

    // if (Logging::DIRECTINPUT_DEBUG)
    //     Logger::Write(L"Windowsmouse initialization..\n");

    // initialize_windowsmouse();

    // Logger::Write(L"Catweasel joymouse initialization..\n");
    // initialize_catweasel();

    //  Logger::Write(L"Parallel joystick port initialization..\n");
    //  initialize_parjoyport();

    // if (Logging::CONSOLE_MORE_INFO)
    //     Logger::Write(L"wintab tablet initialization..\n");

    // #ifdef TABLET
    // initialize_tablet();
    // #endif

    if (Logging::CONSOLE_MORE_INFO)
        Logger::Write(L"end\n");

    sortdd(di_joystick, num_joystick, DID_JOYSTICK);
    sortdd(di_mouse, num_mouse, DID_MOUSE);
    sortdd(di_keyboard, num_keyboard, DID_KEYBOARD);

    return 1;
}

static int di_init()
{
    if (dd_inited++ > 0)
        return 1;
    if (!di_do_init())
        return 0;
    return 1;
}

static void di_free()
{
    // int i;
    // if (dd_inited == 0)
    //     return;
    // dd_inited--;
    // if (dd_inited > 0)
    //     return;
    // if (g_lpdi)
    //     IDirectInput8_Release(g_lpdi);
    // g_lpdi = 0;
    for (int i = 0; i < MAX_INPUT_DEVICES; i++)
    {
        di_dev_free(&di_joystick[i]);
        di_dev_free(&di_mouse[i]);
        di_dev_free(&di_keyboard[i]);
    }
}

static int get_mouse_num()
{
    return num_mouse;
}

static const TCHAR* get_mouse_friendlyname(int mouse)
{
    return di_mouse[mouse].name;
}

static const TCHAR* get_mouse_uniquename(int mouse)
{
    return di_mouse[mouse].configname;
}

static int get_mouse_widget_num(int mouse)
{
    return di_mouse[mouse].axles + di_mouse[mouse].buttons;
}

static int get_mouse_widget_first(int mouse, int type)
{
    switch (type)
    {
        case IDEV_WIDGET_BUTTON:
            return di_mouse[mouse].axles;
        case IDEV_WIDGET_AXIS:
            return 0;
        case IDEV_WIDGET_BUTTONAXIS:
            return di_mouse[mouse].axles + di_mouse[mouse].buttons_real;
    }
    return -1;
}

static int get_mouse_widget_type(int mouse, int num, TCHAR* name, uint* code)
{
    struct didata* did = &di_mouse[mouse];

    int axles = did->axles;
    int buttons = did->buttons;
    int realbuttons = did->buttons_real;
    if (num >= axles + realbuttons && num < axles + buttons)
    {
        if (name)
            _tcscpy(name, did->buttonname[num - axles]);
        return IDEV_WIDGET_BUTTONAXIS;
    }
    else if (num >= axles && num < axles + realbuttons)
    {
        if (name)
            _tcscpy(name, did->buttonname[num - axles]);
        return IDEV_WIDGET_BUTTON;
    }
    else if (num < axles)
    {
        if (name)
            _tcscpy(name, did->axisname[num]);
        return IDEV_WIDGET_AXIS;
    }
    return IDEV_WIDGET_NONE;
}

static int init_mouse()
{
    int i;
    LPDIRECTINPUTDEVICE8 lpdi;
    HRESULT hr;
    struct didata* did;

    if (mouse_inited)
        return 1;
    di_init();
    mouse_inited = 1;
    for (i = 0; i < num_mouse; i++)
    {
        did = &di_mouse[i];
        if (did->connection == DIDC_DX)
        {
            hr = g_lpdi->CreateDevice(did->iguid, &lpdi, nullptr);
            if (SUCCEEDED(hr))
            {
                hr = IDirectInputDevice8_SetDataFormat(lpdi, &c_dfDIMouse2);
                IDirectInputDevice8_EnumObjects(lpdi, EnumObjectsCallback, (void*)did, DIDFT_ALL);
                fixbuttons(did);
                fixthings_mouse(did);
                sortobjects(did);
                did->lpdi = lpdi;
            }
            else
            {
                Logger::Write(L"mouse %d CreateDevice failed, %s\n", i, DXError(hr));
            }
        }
    }
    return 1;
}

static void close_mouse()
{
    int i;

    if (!mouse_inited)
        return;
    mouse_inited = 0;
    for (i = 0; i < num_mouse; i++)
        di_dev_free(&di_mouse[i]);
    supermouse = normalmouse = rawmouse /*= winmouse*/ = 0;
    di_free();
}

static int acquire_mouse(int num, int flags)
{
    LPDIRECTINPUTDEVICE8 lpdi = di_mouse[num].lpdi;
    struct didata* did = &di_mouse[num];
    DIPROPDWORD dipdw;
    HRESULT hr;

    unacquire(lpdi, L"mouse");
    if (did->connection == DIDC_DX && lpdi)
    {
        setcoop(&di_mouse[num], flags ? (DISCL_FOREGROUND | DISCL_EXCLUSIVE) : (DISCL_BACKGROUND | DISCL_NONEXCLUSIVE), L"mouse");
        dipdw.diph.dwSize = sizeof(DIPROPDWORD);
        dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
        dipdw.diph.dwObj = 0;
        dipdw.diph.dwHow = DIPH_DEVICE;
        dipdw.dwData = DI_BUFFER;
        hr = IDirectInputDevice8_SetProperty(lpdi, DIPROP_BUFFERSIZE, &dipdw.diph);
        if (FAILED(hr))
            Logger::Write(L"mouse setpropertry failed, %s\n", DXError(hr));
        di_mouse[num].acquired = acquire(lpdi, L"mouse") ? 1 : -1;
    }
    else
    {
        di_mouse[num].acquired = 1;
    }
    if (di_mouse[num].acquired > 0)
    {
        if (di_mouse[num].rawinput)
            rawmouse++;
        else if (di_mouse[num].superdevice)
            supermouse++;
        // else if (di_mouse[num].wininput)
        // {
        //     winmouse++;
        //     winmousenumber = num;
        //     winmousemode = di_mouse[num].wininput == 2;
        // }
        else
            normalmouse++;
    }
    register_rawinput(flags);
    return di_mouse[num].acquired > 0 ? 1 : 0;
}

static void unacquire_mouse(int num)
{
    unacquire(di_mouse[num].lpdi, L"mouse");
    if (di_mouse[num].acquired > 0)
    {
        if (di_mouse[num].rawinput)
            rawmouse--;
        else if (di_mouse[num].superdevice)
            supermouse--;
        // else if (di_mouse[num].wininput)
        //     winmouse--;
        else
            normalmouse--;
        di_mouse[num].acquired = 0;
    }
}

static void read_mouse()
{
    DIDEVICEOBJECTDATA didod[DI_BUFFER];
    DWORD elements;
    HRESULT hr;
    int i, j, k;
    // int fs = isfullscreen() > 0 ? 1 : 0;
    int istest = inputdevice_istest();

    if (IGNOREEVERYTHING)
        return;

    for (i = 0; i < MAX_INPUT_DEVICES; i++)
    {
        struct didata* did = &di_mouse[i];
        LPDIRECTINPUTDEVICE8 lpdi = did->lpdi;
        if (!did->acquired)
            continue;
        if (did->connection == DIDC_CAT)
        {
            if (getmousestate(i))
            {
                /*int cx, cy, cbuttons;
                   if (catweasel_read_mouse(did->catweasel, &cx, &cy, &cbuttons))
                   {
                   if (cx)
                   setmousestate(i, 0, cx, 0);
                   if (cy)
                   setmousestate(i, 1, cy, 0);
                   setmousebuttonstate(i, 0, cbuttons & 8);
                   setmousebuttonstate(i, 1, cbuttons & 4);
                   setmousebuttonstate(i, 2, cbuttons & 2);
                   }*/
            }
            continue;
        }
        if (!lpdi || did->connection != DIDC_DX)
            continue;
        elements = DI_BUFFER;
        hr = IDirectInputDevice8_GetDeviceData(lpdi, sizeof(DIDEVICEOBJECTDATA), didod, &elements, 0);
        if (SUCCEEDED(hr) || hr == DI_BUFFEROVERFLOW)
        {
            if (supermouse && !did->superdevice)
                continue;
            for (j = 0; j < elements; j++)
            {
                int dimofs = didod[j].dwOfs;
                int data = didod[j].dwData;
                int state = (data & 0x80) ? 1 : 0;
#if DIRECTINPUT2_DEBUG_DEF
                Logger::Write(L"MOUSE: %d OFF=%d DATA=%d STATE=%d\n", i, dimofs, data, state);
#endif
                if (istest || isfocus() > 0)
                {
                    for (k = 0; k < did->axles; k++)
                    {
                        if (did->axismappings[k] == dimofs)
                            setmousestate(i, k, data, 0);
                    }
                    for (k = 0; k < did->buttons; k++)
                    {
                        if (did->buttonmappings[k] == dimofs)
                        {
                            if (did->buttonaxisparent[k] >= 0)
                            {
                                int dir = did->buttonaxisparentdir[k];
                                int bstate = 0;
                                if (dir)
                                    bstate = data > 0 ? 1 : 0;
                                else
                                    bstate = data < 0 ? 1 : 0;
                                if (bstate)
                                    setmousebuttonstate(i, k, -1);
                            }
                            else
                            {
#ifdef SINGLEFILE
                                if (k == 0)
                                    uae_quit();
#endif
                                if ((g_curr_conf.win32_middle_mouse && k != 2) || !(g_curr_conf.win32_middle_mouse))
                                    setmousebuttonstate(i, k, state);
                            }
                        }
                    }
                }
                if (!istest && isfocus() && g_curr_conf.win32_middle_mouse && dimofs == DIMOFS_BUTTON2 && state)
                {
                    if (isfullscreen() > 0)
                        minimizewindow();
                    if (mouseactive)
                        setmouseactive(0);
                }
            }
        }
        else if (hr == DIERR_INPUTLOST)
        {
            acquire(lpdi, L"mouse");
        }
        else if (did->acquired && hr == DIERR_NOTACQUIRED)
        {
            acquire(lpdi, L"mouse");
        }
        IDirectInputDevice8_Poll(lpdi);
    }
}

static int get_mouse_flags(int num)
{
    if (di_mouse[num].rawinput || !rawinput_enabled_mouse)
        return 0;
    // if (di_mouse[num].catweasel)
    //     return 0;
    return 1;
}
struct inputdevice_functions inputdevicefunc_mouse =
{
    init_mouse, close_mouse, acquire_mouse, unacquire_mouse, read_mouse,
    get_mouse_num, get_mouse_friendlyname, get_mouse_uniquename,
    get_mouse_widget_num, get_mouse_widget_type,
    get_mouse_widget_first,
    get_mouse_flags
};

static int get_kb_num()
{
    return num_keyboard;
}

static const TCHAR* get_kb_friendlyname(int kb)
{
    return di_keyboard[kb].name;
}

static const TCHAR* get_kb_uniquename(int kb)
{
    return di_keyboard[kb].configname;
}

static int get_kb_widget_num(int kb)
{
    return di_keyboard[kb].buttons;
}

static int get_kb_widget_first(int kb, int type)
{
    return 0;
}

static int get_kb_widget_type(int kb, int num, TCHAR* name, uint* code)
{
    if (name)
        _tcscpy(name, di_keyboard[kb].buttonname[num]);
    if (code)
        *code = di_keyboard[kb].buttonmappings[num];
    return IDEV_WIDGET_KEY;
}

static int init_kb()
{
    int i;
    LPDIRECTINPUTDEVICE8 lpdi;
    DIPROPDWORD dipdw;
    HRESULT hr;

    if (keyboard_inited)
        return 1;
    di_init();
    oldusedleds = -1;
    keyboard_inited = 1;
    for (i = 0; i < num_keyboard; i++)
    {
        struct didata* did = &di_keyboard[i];
        if (did->connection == DIDC_DX)
        {
            hr = g_lpdi->CreateDevice(did->iguid, &lpdi, nullptr);
            if (SUCCEEDED(hr))
            {
                hr = lpdi->SetDataFormat(&c_dfDIKeyboard);
                if (FAILED(hr))
                    Logger::Write(L"keyboard setdataformat failed, %s\n", DXError(hr));
                memset(&dipdw, 0, sizeof(dipdw));
                dipdw.diph.dwSize = sizeof(DIPROPDWORD);
                dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
                dipdw.diph.dwObj = 0;
                dipdw.diph.dwHow = DIPH_DEVICE;
                dipdw.dwData = DI_KBBUFFER;
                hr = lpdi->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);
                if (FAILED(hr))
                    Logger::Write(L"keyboard setpropertry failed, %s\n", DXError(hr));
                lpdi->EnumObjects(EnumObjectsCallback, did, DIDFT_ALL);
                sortobjects(did);
                did->lpdi = lpdi;
            }
            else
                Logger::Write(L"keyboard CreateDevice failed, %s\n", DXError(hr));
        }
    }
    keyboard_german = 0;
    if ((LOWORD(GetKeyboardLayout(0)) & 0x3ff) == 7)
        keyboard_german = 1;
    return 1;
}

static void close_kb()
{
    int i;

    if (keyboard_inited == 0)
        return;
    keyboard_inited = 0;

    for (i = 0; i < num_keyboard; i++)
        di_dev_free(&di_keyboard[i]);
    superkb = normalkb = rawkb = 0;
    di_free();
}

static uint kb_do_refresh;

int ispressed(int key)
{
    int i;
    for (i = 0; i < MAX_INPUT_DEVICES; i++)
    {
        if (di_keycodes[i][key])
            return 1;
    }
    return 0;
}

static void release_keys()
{
    int i, j;

    for (j = 0; j < MAX_INPUT_DEVICES; j++)
    {
        for (i = 0; i < MAX_KEYCODES; i++)
        {
            if (di_keycodes[j][i])
            {
                my_kbd_handler(j, i, 0);
                di_keycodes[j][i] = 0;
            }
        }
    }
    memset(rawkeystate, 0, sizeof rawkeystate);
}

static int acquire_kb(int num, int flags)
{
    LPDIRECTINPUTDEVICE8 lpdi = di_keyboard[num].lpdi;

    unacquire(lpdi, L"keyboard");
    if (g_curr_conf.keyboard_leds_in_use)
    {
        // #ifdef WINDDK
        if (!g_curr_conf.win32_kbledmode)
        {
            if (DefineDosDevice(DDD_RAW_TARGET_PATH, L"Kbd", L"\\Device\\KeyboardClass0"))
            {
                kbhandle = CreateFile(L"\\\\.\\Kbd", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
                if (kbhandle == INVALID_HANDLE_VALUE)
                {
                    Logger::Write(L"kbled: CreateFile failed, error %d\n", GetLastError());
                    g_curr_conf.win32_kbledmode = 1;
                }
            }
            else
            {
                g_curr_conf.win32_kbledmode = 1;
                Logger::Write(L"kbled: DefineDosDevice failed, error %d\n", GetLastError());
            }
        }
        // #else
        // g_curr_conf.kbledmode = 1;
        // #endif
        oldleds = get_leds();
        if (oldusedleds < 0)
            oldusedleds = newleds = oldleds;
        set_leds(oldusedleds);
    }

    // lock_kb();
    setcoop(&di_keyboard[num], DISCL_NOWINKEY | DISCL_FOREGROUND | DISCL_EXCLUSIVE, L"keyboard");
    kb_do_refresh = ~0;
    di_keyboard[num].acquired = -1;
    if (acquire(lpdi, L"keyboard"))
    {
        if (di_keyboard[num].rawinput)
            rawkb++;
        else if (di_keyboard[num].superdevice)
            superkb++;
        else
            normalkb++;
        di_keyboard[num].acquired = 1;
    }
    register_rawinput(flags);
    return di_keyboard[num].acquired > 0 ? 1 : 0;
}

static void unacquire_kb(int num)
{
    LPDIRECTINPUTDEVICE8 lpdi = di_keyboard[num].lpdi;

    unacquire(lpdi, L"keyboard");
    if (di_keyboard[num].acquired > 0)
    {
        if (di_keyboard[num].rawinput)
            rawkb--;
        else if (di_keyboard[num].superdevice)
            superkb--;
        else
            normalkb--;
        di_keyboard[num].acquired = 0;
    }

    if (g_curr_conf.keyboard_leds_in_use)
    {
        if (oldusedleds >= 0)
        {
            set_leds(oldleds);
            oldusedleds = oldleds;
        }
        // #ifdef WINDDK
        if (kbhandle != INVALID_HANDLE_VALUE)
        {
            CloseHandle(kbhandle);
            DefineDosDevice(DDD_REMOVE_DEFINITION, L"Kbd", nullptr);
            kbhandle = INVALID_HANDLE_VALUE;
        }
        // #endif
    }
    // unlock_kb();
}

static int refresh_kb(LPDIRECTINPUTDEVICE8 lpdi, int num)
{
    HRESULT hr;
    int i;
    byte kc[256];

    hr = IDirectInputDevice8_GetDeviceState(lpdi, sizeof(kc), kc);
    if (SUCCEEDED(hr))
    {
        for (i = 0; i < sizeof(kc); i++)
        {
            if (i == 0x80) /* USB KB led causes this, better ignore it */
                continue;
            if (kc[i] & 0x80)
                kc[i] = 1;
            else
                kc[i] = 0;
            if (kc[i] != di_keycodes[num][i])
            {
                Logger::Write(L"%d: %02X -> %d\n", num, i, kc[i]);
                di_keycodes[num][i] = kc[i];
                my_kbd_handler(num, i, kc[i]);
            }
        }
    }
    else if (hr == DIERR_INPUTLOST)
    {
        acquire_kb(num, 0);
        IDirectInputDevice8_Poll(lpdi);
        return 0;
    }
    IDirectInputDevice8_Poll(lpdi);
    return 1;
}

static void read_kb()
{
    DIDEVICEOBJECTDATA didod[DI_KBBUFFER];
    DWORD elements;
    HRESULT hr;
    LPDIRECTINPUTDEVICE8 lpdi;
    int i, j;
    int istest = inputdevice_istest();

    if (IGNOREEVERYTHING)
        return;

    update_leds();
    for (i = 0; i < MAX_INPUT_DEVICES; i++)
    {
        struct didata* did = &di_keyboard[i];
        if (!did->acquired)
            continue;
        if (istest == 0 && isfocus() == 0)
        {
            if (did->acquired > 0)
                unacquire_kb(i);
            continue;
        }
        lpdi = did->lpdi;
        if (!lpdi)
            continue;
        if (kb_do_refresh & (1 << i))
        {
            if (!refresh_kb(lpdi, i))
                continue;
            kb_do_refresh &= ~(1 << i);
        }
        elements = DI_KBBUFFER;
        hr = IDirectInputDevice8_GetDeviceData(lpdi, sizeof(DIDEVICEOBJECTDATA), didod, &elements, 0);
        if ((SUCCEEDED(hr) || hr == DI_BUFFEROVERFLOW) && (isfocus() || istest))
        {
            for (j = 0; j < elements; j++)
            {
                int scancode = didod[j].dwOfs;
                int pressed = (didod[j].dwData & 0x80) ? 1 : 0;
                // Logger::Write (L"%d: %02X %d\n", j, scancode, pressed);
                if (!istest)
                    scancode = keyhack(scancode, pressed, i);
                if (scancode < 0)
                    continue;
                di_keycodes[i][scancode] = pressed;
                if (istest)
                {
                    if (pressed && (scancode == DIK_F12 || scancode == DIK_F11))
                        return;
                    if (scancode == DIK_F12)
                        scancode = -1;
                    if (scancode == DIK_F11)
                    {
                        inputdevice_testrecord(IDTYPE_KEYBOARD, i, IDEV_WIDGET_BUTTON, 0x100, 1);
                        inputdevice_testrecord(IDTYPE_KEYBOARD, i, IDEV_WIDGET_BUTTON, 0x100, 0);
                    }
                    else
                    {
                        inputdevice_testrecord(IDTYPE_KEYBOARD, i, IDEV_WIDGET_BUTTON, scancode, pressed);
                    }
                }
                else
                {
                    if (stopoutput == 0)
                        my_kbd_handler(i, scancode, pressed);
                }
            }
        }
        else if (hr == DIERR_INPUTLOST)
        {
            acquire_kb(i, 0);
            kb_do_refresh |= 1 << i;
        }
        else if (did->acquired && hr == DIERR_NOTACQUIRED)
        {
            acquire_kb(i, 0);
        }
        IDirectInputDevice8_Poll(lpdi);
    }
    // #ifdef CATWEASEL
    // if (isfocus() || istest)
    // {
    //     byte kc;
    //     if (stopoutput == 0 && catweasel_read_keyboard(&kc))
    //         inputdevice_do_keyboard(kc & 0x7f, kc & 0x80);
    // }
    // #endif
}

void wait_keyrelease()
{
    stopoutput++;

#if 0
    int i, j, maxcount = 10, found;
    while (maxcount-- > 0)
    {
        read_kb();
        found = 0;
        for (j = 0; j < MAX_INPUT_DEVICES; j++)
        {
            for (i = 0; i < MAX_KEYCODES; i++)
            {
                if (di_keycodes[j][i])
                    found = 1;
            }
        }
        if (!found)
            break;
        sleep_millis(10);
    }
#endif
    release_keys();
#if 0
    for (;;)
    {
        int ok = 0, nok = 0;
        for (i = 0; i < MAX_INPUT_DEVICES; i++)
        {
            struct didata* did = &di_mouse[i];
            DIMOUSESTATE dis;
            LPDIRECTINPUTDEVICE8 lpdi = did->lpdi;
            HRESULT hr;
            int j;

            if (!lpdi)
                continue;
            nok++;
            hr = IDirectInputDevice8_GetDeviceState(lpdi, sizeof(dis), &dis);
            if (SUCCEEDED(hr))
            {
                for (j = 0; j < 4; j++)
                {
                    if (dis.rgbButtons[j] & 0x80)
                        break;
                }
                if (j == 4)
                    ok++;
            }
            else
            {
                ok++;
            }
        }
        if (ok == nok)
            break;
        sleep_millis(10);
    }
#endif
    stopoutput--;
}

static int get_kb_flags(int num)
{
    return 0;
}

struct inputdevice_functions inputdevicefunc_keyboard =
{
    init_kb, close_kb, acquire_kb, unacquire_kb, read_kb,
    get_kb_num, get_kb_friendlyname, get_kb_uniquename,
    get_kb_widget_num, get_kb_widget_type,
    get_kb_widget_first,
    get_kb_flags
};

static int get_joystick_num()
{
    return num_joystick;
}

static int get_joystick_widget_num(int joy)
{
    return di_joystick[joy].axles + di_joystick[joy].buttons;
}

static int get_joystick_widget_type(int joy, int num, TCHAR* name, uint* code)
{
    struct didata* did = &di_joystick[joy];
    if (num >= did->axles + did->buttons_real && num < did->axles + did->buttons)
    {
        if (name)
            _tcscpy(name, did->buttonname[num - did->axles]);
        return IDEV_WIDGET_BUTTONAXIS;
    }
    else if (num >= did->axles && num < did->axles + did->buttons_real)
    {
        if (name)
            _tcscpy(name, did->buttonname[num - did->axles]);
        return IDEV_WIDGET_BUTTON;
    }
    else if (num < di_joystick[joy].axles)
    {
        if (name)
            _tcscpy(name, did->axisname[num]);
        return IDEV_WIDGET_AXIS;
    }
    return IDEV_WIDGET_NONE;
}

static int get_joystick_widget_first(int joy, int type)
{
    switch (type)
    {
        case IDEV_WIDGET_BUTTON:
            return di_joystick[joy].axles;
        case IDEV_WIDGET_AXIS:
            return 0;
        case IDEV_WIDGET_BUTTONAXIS:
            return di_joystick[joy].axles + di_joystick[joy].buttons_real;
    }
    return -1;
}

static const TCHAR* get_joystick_friendlyname(int joy)
{
    return di_joystick[joy].name;
}

static const TCHAR* get_joystick_uniquename(int joy)
{
    return di_joystick[joy].configname;
}

static void read_joystick()
{
    DIDEVICEOBJECTDATA didod[DI_BUFFER];
    LPDIRECTINPUTDEVICE8 lpdi;
    DWORD elements;
    HRESULT hr;
    int i, j, k;
    int istest = inputdevice_istest();

    if (IGNOREEVERYTHING)
        return;

    for (i = 0; i < MAX_INPUT_DEVICES; i++)
    {
        struct didata* did = &di_joystick[i];
        if (!did->acquired)
            continue;
        if (did->connection == DIDC_CAT)
        {
            if (getjoystickstate(i) && (isfocus() || istest))
            {
                /* only read CW state if it is really needed */
                /*byte cdir, cbuttons;
                   if (catweasel_read_joystick(&cdir, &cbuttons))
                   {
                   cdir >>= did->catweasel * 4;
                   cbuttons >>= did->catweasel * 4;
                   setjoystickstate(i, 0, !(cdir & 1) ? 1 : !(cdir & 2) ? -1 : 0, 0);
                   setjoystickstate(i, 1, !(cdir & 4) ? 1 : !(cdir & 8) ? -1 : 0, 0);
                   setjoybuttonstate(i, 0, cbuttons & 8);
                   setjoybuttonstate(i, 1, cbuttons & 4);
                   setjoybuttonstate(i, 2, cbuttons & 2);
                   }*/
            }
            continue;
        }
        else if (did->connection == DIDC_PARJOY)
        {
            DWORD ret;
            PAR_QUERY_INFORMATION inf;
            ret = 0;
            if (DeviceIoControl(did->parjoy, IOCTL_PAR_QUERY_INFORMATION, nullptr, 0, &inf, sizeof inf, &ret, nullptr))
            {
                Logger::Write(L"PARJOY: IOCTL_PAR_QUERY_INFORMATION = %u\n", GetLastError());
            }
            else
            {
                if (inf.Status != did->oldparjoystatus.Status)
                {
                    Logger::Write(L"PARJOY: %08x\n", inf.Status);
                    did->oldparjoystatus.Status = inf.Status;
                }
            }
            continue;
#if 0
        }
        else if (did->connection == DIDC_RAW)
        {
            bool changedreport = true;
            while ((isfocus() || istest) && changedreport)
            {
                DWORD readbytes;
                changedreport = false;
                if (!did->hidread)
                {
                    DWORD ret;
                    DWORD readlen = did->hidcaps.InputReportByteLength;
                    did->hidbuffer[0] = 0;
                    ret = ReadFile(did->hidhandle, did->hidbuffer, readlen, nullptr, &did->hidol);
                    if (ret || (ret == 0 && GetLastError() == ERROR_IO_PENDING))
                        did->hidread = true;
                }
                if (did->hidread && GetOverlappedResult(did->hidhandle, &did->hidol, &readbytes, FALSE))
                {
                    did->hidread = false;
                    changedreport = memcmp(did->hidbuffer, did->hidbufferprev, readbytes) != 0;
                    memcpy(did->hidbufferprev, did->hidbuffer, readbytes);
                    DWORD usagelength = did->maxusagelistlength;
                    if (changedreport)
                        Logger::Write(L"%02x%02x%02x%02x%02x%02x%02x\n",
                            (byte)did->hidbuffer[0], (byte)did->hidbuffer[1], (byte)did->hidbuffer[2], (byte)did->hidbuffer[3], (byte)did->hidbuffer[4], (byte)did->hidbuffer[5], (byte)did->hidbuffer[6]);
                    if (HidP_GetUsagesEx(HidP_Input, 0, did->usagelist, &usagelength, did->hidpreparseddata, did->hidbuffer, readbytes) == HIDP_STATUS_SUCCESS)
                    {
                        for (k = 0; k < usagelength; k++)
                        {
                            for (j = 0; j < did->maxusagelistlength; j++)
                            {
                                if (did->usagelist[k].UsagePage == did->prevusagelist[j].UsagePage &&
                                    did->usagelist[k].Usage == did->prevusagelist[j].Usage)
                                    break;
                            }
                            if (j == did->maxusagelistlength || did->prevusagelist[j].Usage == 0)
                            {
                                Logger::Write(L"%d/%d ON\n", did->usagelist[k].UsagePage, did->usagelist[k].Usage);
                            }
                            else
                            {
                                did->prevusagelist[j].Usage = 0;
                            }
                        }
                        for (j = 0; j < did->maxusagelistlength; j++)
                        {
                            if (did->prevusagelist[j].Usage)
                            {
                                Logger::Write(L"%d/%d OFF\n", did->prevusagelist[j].UsagePage, did->prevusagelist[j].Usage);
                            }
                        }
                        memcpy(did->prevusagelist, did->usagelist, usagelength * sizeof USAGE_AND_PAGE);
                        memset(did->prevusagelist + usagelength, 0, (did->maxusagelistlength - usagelength) * sizeof USAGE_AND_PAGE);
                    }
                }
            }
#endif
        }

        lpdi = did->lpdi;
        if (!lpdi || did->connection != DIDC_DX)
            continue;
        elements = DI_BUFFER;
        hr = IDirectInputDevice8_GetDeviceData(lpdi, sizeof(DIDEVICEOBJECTDATA), didod, &elements, 0);
        if ((SUCCEEDED(hr) || hr == DI_BUFFEROVERFLOW) && (isfocus() || istest))
        {
            for (j = 0; j < elements; j++)
            {
                int dimofs = didod[j].dwOfs;
                int data = didod[j].dwData;
                int data2 = data;
                int state = (data & 0x80) ? 1 : 0;
                data -= 32768;

                for (k = 0; k < did->buttons; k++)
                {
                    if (did->buttonaxisparent[k] >= 0 && did->buttonmappings[k] == dimofs)
                    {
                        int bstate = -1;
                        int axis = did->buttonaxisparent[k];
                        int dir = did->buttonaxisparentdir[k];

                        if (did->axistype[axis] == 1)
                        {
                            if (!dir)
                                bstate = (data2 >= 20250 && data2 <= 33750) ? 1 : 0;
                            else
                                bstate = (data2 >= 2250 && data2 <= 15750) ? 1 : 0;
                        }
                        else if (did->axistype[axis] == 2)
                        {
                            if (!dir)
                                bstate = ((data2 >= 29250 && data2 <= 33750) || (data2 >= 0 && data2 <= 6750)) ? 1 : 0;
                            else
                                bstate = (data2 >= 11250 && data2 <= 24750) ? 1 : 0;
                        }
                        else if (did->axistype[axis] == 0)
                        {
                            if (dir)
                                bstate = data > 20000 ? 1 : 0;
                            else
                                bstate = data < -20000 ? 1 : 0;
                        }
                        if (bstate >= 0 && axisold[i][k] != bstate)
                        {
                            setjoybuttonstate(i, k, bstate);
                            axisold[i][k] = bstate;
#if DIRECTINPUT2_DEBUG_DEF
                            Logger::Write(L"AB:NUM=%d OFF=%d AXIS=%d DIR=%d NAME=%s VAL=%d STATE=%d BS=%d\n",
                                k, dimofs, axis, dir, did->buttonname[k], data, state, bstate);
#endif
                        }
                    }
                    else if (did->buttonaxisparent[k] < 0 && did->buttonmappings[k] == dimofs)
                    {
#if DIRECTINPUT2_DEBUG_DEF
                        Logger::Write(L"B:NUM=%d OFF=%d NAME=%s VAL=%d STATE=%d\n",
                            k, dimofs, did->buttonname[k], data, state);
#endif
                        setjoybuttonstate(i, k, state);
                    }
                }

                for (k = 0; k < did->axles; k++)
                {
                    if (did->axismappings[k] == dimofs)
                    {
                        if (did->axistype[k] == 1)
                        {
                            setjoystickstate(i, k, (data2 >= 20250 && data2 <= 33750) ? -1 : (data2 >= 2250 && data2 <= 15750) ? 1 : 0, 1);
                        }
                        else if (did->axistype[k] == 2)
                        {
                            setjoystickstate(i, k, ((data2 >= 29250 && data2 <= 33750) || (data2 >= 0 && data2 <= 6750)) ? -1 : (data2 >= 11250 && data2 <= 24750) ? 1 : 0, 1);
#if DIRECTINPUT2_DEBUG_DEF
                            Logger::Write(L"P:NUM=%d OFF=%d NAME=%s VAL=%d\n", k, dimofs, did->axisname[k], data2);
#endif
                        }
                        else if (did->axistype[k] == 0)
                        {
#if DIRECTINPUT2_DEBUG_DEF
                            if (data < -20000 || data > 20000)
                                Logger::Write(L"A:NUM=%d OFF=%d NAME=%s VAL=%d\n", k, dimofs, did->axisname[k], data);
#endif
                            if (istest)
                            {
                                if (data < -20000)
                                    data = -20000;
                                else if (data > 20000)
                                    data = 20000;
                                else
                                    data = 0;
                            }
                            if (axisold[i][k] != data)
                            {
                                setjoystickstate(i, k, data, 32767);
                                axisold[i][k] = data;
                            }
                        }
                    }
                }
            }
        }
        else if (hr == DIERR_INPUTLOST)
        {
            acquire(lpdi, L"joystick");
        }
        else if (did->acquired && hr == DIERR_NOTACQUIRED)
        {
            acquire(lpdi, L"joystick");
        }
        IDirectInputDevice8_Poll(lpdi);
    }
}

static int init_joystick()
{
    int i;
    LPDIRECTINPUTDEVICE8 lpdi;
    HRESULT hr;
    struct didata* did;

    if (joystick_inited)
        return 1;
    di_init();
    joystick_inited = 1;
    for (i = 0; i < num_joystick; i++)
    {
        did = &di_joystick[i];
        if (did->connection == DIDC_DX)
        {
            hr = g_lpdi->CreateDevice(did->iguid, &lpdi, nullptr);
            if (SUCCEEDED(hr))
            {
                hr = lpdi->SetDataFormat(&c_dfDIJoystick);
                if (SUCCEEDED(hr))
                {
                    did->lpdi = lpdi;
                    lpdi->EnumObjects(EnumObjectsCallback, (void*)did, DIDFT_ALL);
                    fixbuttons(did);
                    fixthings(did);
                    sortobjects(did);
                }
            }
            else
            {
                Logger::Write(L"joystick createdevice failed, %s\n", DXError(hr));
            }
        }
    }
    return 1;
}

static void close_joystick()
{
    int i;

    if (!joystick_inited)
        return;
    joystick_inited = 0;
    for (i = 0; i < num_joystick; i++)
        di_dev_free(&di_joystick[i]);
    rawhid = 0;
    di_free();
}

void dinput_window()
{
    int i;
    for (i = 0; i < num_joystick; i++)
        di_joystick[i].coop = 0;
    for (i = 0; i < num_mouse; i++)
        di_mouse[i].coop = 0;
    for (i = 0; i < num_keyboard; i++)
        di_keyboard[i].coop = 0;
}

static int acquire_joystick(int num, int flags)
{
    LPDIRECTINPUTDEVICE8 lpdi = di_joystick[num].lpdi;
    DIPROPDWORD dipdw;
    HRESULT hr;

    unacquire(lpdi, L"joystick");
    if (di_joystick[num].connection == DIDC_DX && lpdi)
    {
        setcoop(&di_joystick[num], flags ? (DISCL_FOREGROUND | DISCL_EXCLUSIVE) : (DISCL_BACKGROUND | DISCL_NONEXCLUSIVE), L"joystick");
        memset(&dipdw, 0, sizeof(dipdw));
        dipdw.diph.dwSize = sizeof(DIPROPDWORD);
        dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
        dipdw.diph.dwObj = 0;
        dipdw.diph.dwHow = DIPH_DEVICE;
        dipdw.dwData = DI_BUFFER;
        hr = IDirectInputDevice8_SetProperty(lpdi, DIPROP_BUFFERSIZE, &dipdw.diph);
        if (FAILED(hr))
            Logger::Write(L"joystick setproperty failed, %s\n", DXError(hr));
        di_joystick[num].acquired = acquire(lpdi, L"joystick") ? 1 : -1;
    }
    else if (di_joystick[num].connection == DIDC_RAW)
    {
        rawhid++;
        di_joystick[num].acquired = 1;
    }
    else
    {
        di_joystick[num].acquired = 1;
    }
    register_rawinput(flags);
    return di_joystick[num].acquired > 0 ? 1 : 0;
}

static void unacquire_joystick(int num)
{
    struct didata* did = &di_joystick[num];
    unacquire(did->lpdi, L"joystick");
    if (did->connection == DIDC_RAW)
    {
        if (di_joystick[num].acquired)
            rawhid--;
    }
    di_joystick[num].acquired = 0;
}

static int get_joystick_flags(int num)
{
    return 0;
}

struct inputdevice_functions inputdevicefunc_joystick =
{
    init_joystick, close_joystick, acquire_joystick, unacquire_joystick,
    read_joystick, get_joystick_num, get_joystick_friendlyname, get_joystick_uniquename,
    get_joystick_widget_num, get_joystick_widget_type,
    get_joystick_widget_first,
    get_joystick_flags
};

int dinput_wmkey(uint key)
{
    if (normalkb || superkb || rawkb)
        return 0;
    if (((key >> 16) & 0xff) == 0x58)
        return 1;
    return 0;
}

int input_get_default_keyboard(int i)
{
    if (rawinput_enabled_keyboard)
    {
        return 1;
    }
    else
    {
        if (i == 0)
            return 1;
        return 0;
    }
}

static void setid(UaeInputDevice* uid, int i, int slot, int sub, int port, int evt)
{
    //  wrong place!
    uid->eventid[slot][SPARE_SUB_EVENT] = uid->eventid[slot][sub];
    uid->flags[slot][SPARE_SUB_EVENT] = uid->flags[slot][sub];
    uid->port[slot][SPARE_SUB_EVENT] = MAX_JPORTS + 1;
    // free(uid->custom[slot][SPARE_SUB_EVENT]);
    uid->custom[slot][SPARE_SUB_EVENT] = uid->custom[slot][sub];
    uid->custom[slot][sub] = "";

    uid[i].eventid[slot][sub] = evt;
    uid[i].port[slot][sub] = port + 1;
}

static void setid(UaeInputDevice* uid, int i, int slot, int sub, int port, int evt, int af)
{
    setid(uid, i, slot, sub, port, evt);
    uid[i].flags[slot][sub] &= ~ID_FLAG_AUTOFIRE_MASK;
    if (af >= JPORT_AF_NORMAL)
        uid[i].flags[slot][sub] |= ID_FLAG_AUTOFIRE;
    if (af == JPORT_AF_TOGGLE)
        uid[i].flags[slot][sub] |= ID_FLAG_TOGGLE;
    if (af == JPORT_AF_ALWAYS)
        uid[i].flags[slot][sub] |= ID_FLAG_INVERTTOGGLE;
}

int input_get_default_mouse(UaeInputDevice* uid, int i, int port, int af)
{
    struct didata* did;

    if (i >= num_mouse)
        return 0;
    did = &di_mouse[i];
    setid(uid, i, ID_AXIS_OFFSET + 0, 0, port, port ? INPUTEVENT_MOUSE2_HORIZ : INPUTEVENT_MOUSE1_HORIZ);
    setid(uid, i, ID_AXIS_OFFSET + 1, 0, port, port ? INPUTEVENT_MOUSE2_VERT : INPUTEVENT_MOUSE1_VERT);
    setid(uid, i, ID_AXIS_OFFSET + 2, 0, port, port ? 0 : INPUTEVENT_MOUSE1_WHEEL);
    setid(uid, i, ID_BUTTON_OFFSET + 0, 0, port, port ? INPUTEVENT_JOY2_FIRE_BUTTON : INPUTEVENT_JOY1_FIRE_BUTTON, af);
    setid(uid, i, ID_BUTTON_OFFSET + 1, 0, port, port ? INPUTEVENT_JOY2_2ND_BUTTON : INPUTEVENT_JOY1_2ND_BUTTON);
    setid(uid, i, ID_BUTTON_OFFSET + 2, 0, port, port ? INPUTEVENT_JOY2_3RD_BUTTON : INPUTEVENT_JOY1_3RD_BUTTON);
    if (port == 0) /* map back and forward to ALT+LCUR and ALT+RCUR */
    {
        if (isrealbutton(did, 3))
        {
            setid(uid, i, ID_BUTTON_OFFSET + 3, 0, port, INPUTEVENT_KEY_ALT_LEFT);
            setid(uid, i, ID_BUTTON_OFFSET + 3, 1, port, INPUTEVENT_KEY_CURSOR_LEFT);
            if (isrealbutton(did, 4))
            {
                setid(uid, i, ID_BUTTON_OFFSET + 4, 0, port, INPUTEVENT_KEY_ALT_LEFT);
                setid(uid, i, ID_BUTTON_OFFSET + 4, 1, port, INPUTEVENT_KEY_CURSOR_RIGHT);
            }
        }
    }
    if (i == 0)
        return 1;
    return 0;
}

int input_get_default_lightpen(UaeInputDevice* uid, int i, int port, int af)
{
    struct didata* did;

    if (i >= num_mouse)
        return 0;
    did = &di_mouse[i];
    setid(uid, i, ID_AXIS_OFFSET + 0, 0, port, INPUTEVENT_LIGHTPEN_HORIZ);
    setid(uid, i, ID_AXIS_OFFSET + 1, 0, port, INPUTEVENT_LIGHTPEN_VERT);
    setid(uid, i, ID_BUTTON_OFFSET + 0, 0, port, port ? INPUTEVENT_JOY2_3RD_BUTTON : INPUTEVENT_JOY1_3RD_BUTTON);
    if (i == 0)
        return 1;
    return 0;
}

int input_get_default_joystick(UaeInputDevice* uid, int i, int port, int af, int mode)
{
    int j;
    struct didata* did;
    int h, v;

    if (i >= num_joystick)
        return 0;
    did = &di_joystick[i];
    if (mode == JSEM_MODE_MOUSE_CDTV)
    {
        h = INPUTEVENT_MOUSE_CDTV_HORIZ;
        v = INPUTEVENT_MOUSE_CDTV_VERT;
    }
    else if (port >= 2)
    {
        h = port == 3 ? INPUTEVENT_PAR_JOY2_HORIZ : INPUTEVENT_PAR_JOY1_HORIZ;
        v = port == 3 ? INPUTEVENT_PAR_JOY2_VERT : INPUTEVENT_PAR_JOY1_VERT;
    }
    else
    {
        h = port ? INPUTEVENT_JOY2_HORIZ : INPUTEVENT_JOY1_HORIZ;
        v = port ? INPUTEVENT_JOY2_VERT : INPUTEVENT_JOY1_VERT;
    }
    setid(uid, i, ID_AXIS_OFFSET + 0, 0, port, h);
    setid(uid, i, ID_AXIS_OFFSET + 1, 0, port, v);

    if (port >= 2)
    {
        setid(uid, i, ID_BUTTON_OFFSET + 0, 0, port, port == 3 ? INPUTEVENT_PAR_JOY2_FIRE_BUTTON : INPUTEVENT_PAR_JOY1_FIRE_BUTTON, af);
    }
    else
    {
        setid(uid, i, ID_BUTTON_OFFSET + 0, 0, port, port ? INPUTEVENT_JOY2_FIRE_BUTTON : INPUTEVENT_JOY1_FIRE_BUTTON, af);
        if (isrealbutton(did, 1))
            setid(uid, i, ID_BUTTON_OFFSET + 1, 0, port, port ? INPUTEVENT_JOY2_2ND_BUTTON : INPUTEVENT_JOY1_2ND_BUTTON);
        if (isrealbutton(did, 2))
            setid(uid, i, ID_BUTTON_OFFSET + 2, 0, port, port ? INPUTEVENT_JOY2_3RD_BUTTON : INPUTEVENT_JOY1_3RD_BUTTON);
    }

    for (j = 2; j < MAX_MAPPINGS - 1; j++)
    {
        int type = did->axistype[j];
        if (type == AXISTYPE_POV_X)
        {
            setid(uid, i, ID_AXIS_OFFSET + j + 0, 0, port, h);
            setid(uid, i, ID_AXIS_OFFSET + j + 1, 0, port, v);
            j++;
        }
    }
    if (mode == JSEM_MODE_JOYSTICK_CD32)
    {
        setid(uid, i, ID_BUTTON_OFFSET + 0, 0, port, port ? INPUTEVENT_JOY2_CD32_RED : INPUTEVENT_JOY1_CD32_RED, af);
        setid(uid, i, ID_BUTTON_OFFSET + 0, 1, port, port ? INPUTEVENT_JOY2_FIRE_BUTTON : INPUTEVENT_JOY1_FIRE_BUTTON, af);
        if (isrealbutton(did, 1))
        {
            setid(uid, i, ID_BUTTON_OFFSET + 1, 0, port, port ? INPUTEVENT_JOY2_CD32_BLUE : INPUTEVENT_JOY1_CD32_BLUE);
            setid(uid, i, ID_BUTTON_OFFSET + 1, 1, port, port ? INPUTEVENT_JOY2_2ND_BUTTON : INPUTEVENT_JOY1_2ND_BUTTON);
        }
        if (isrealbutton(did, 2))
            setid(uid, i, ID_BUTTON_OFFSET + 2, 0, port, port ? INPUTEVENT_JOY2_CD32_GREEN : INPUTEVENT_JOY1_CD32_GREEN);
        if (isrealbutton(did, 3))
            setid(uid, i, ID_BUTTON_OFFSET + 3, 0, port, port ? INPUTEVENT_JOY2_CD32_YELLOW : INPUTEVENT_JOY1_CD32_YELLOW);
        if (isrealbutton(did, 4))
            setid(uid, i, ID_BUTTON_OFFSET + 4, 0, port, port ? INPUTEVENT_JOY2_CD32_RWD : INPUTEVENT_JOY1_CD32_RWD);
        if (isrealbutton(did, 5))
            setid(uid, i, ID_BUTTON_OFFSET + 5, 0, port, port ? INPUTEVENT_JOY2_CD32_FFW : INPUTEVENT_JOY1_CD32_FFW);
        if (isrealbutton(did, 6))
            setid(uid, i, ID_BUTTON_OFFSET + 6, 0, port, port ? INPUTEVENT_JOY2_CD32_PLAY : INPUTEVENT_JOY1_CD32_PLAY);
    }
    if (i == 0)
        return 1;
    return 0;
}

int input_get_default_joystick_analog(UaeInputDevice* uid, int i, int port, int af)
{
    int j;
    struct didata* did;

    if (i >= num_joystick)
        return 0;
    did = &di_joystick[i];
    setid(uid, i, ID_AXIS_OFFSET + 0, 0, port, port ? INPUTEVENT_JOY2_HORIZ_POT : INPUTEVENT_JOY1_HORIZ_POT);
    setid(uid, i, ID_AXIS_OFFSET + 1, 0, port, port ? INPUTEVENT_JOY2_VERT_POT : INPUTEVENT_JOY1_VERT_POT);
    setid(uid, i, ID_BUTTON_OFFSET + 0, 0, port, port ? INPUTEVENT_JOY2_LEFT : INPUTEVENT_JOY1_LEFT, af);
    if (isrealbutton(did, 1))
        setid(uid, i, ID_BUTTON_OFFSET + 1, 0, port, port ? INPUTEVENT_JOY2_RIGHT : INPUTEVENT_JOY1_RIGHT);
    if (isrealbutton(did, 2))
        setid(uid, i, ID_BUTTON_OFFSET + 2, 0, port, port ? INPUTEVENT_JOY2_UP : INPUTEVENT_JOY1_UP);
    if (isrealbutton(did, 3))
        setid(uid, i, ID_BUTTON_OFFSET + 3, 0, port, port ? INPUTEVENT_JOY2_DOWN : INPUTEVENT_JOY1_DOWN);
    for (j = 2; j < MAX_MAPPINGS - 1; j++)
    {
        int type = did->axistype[j];
        if (type == AXISTYPE_POV_X)
        {
            setid(uid, i, ID_AXIS_OFFSET + j + 0, 0, port, port ? INPUTEVENT_JOY2_HORIZ_POT : INPUTEVENT_JOY1_HORIZ_POT);
            setid(uid, i, ID_AXIS_OFFSET + j + 1, 0, port, port ? INPUTEVENT_JOY2_VERT_POT : INPUTEVENT_JOY1_VERT_POT);
            j++;
        }
    }
    if (i == 0)
        return 1;
    return 0;
}